[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6MX322TD9HQPCYSNTFB4HBA":3,"skill-same:01M6MX322TD9HQPCYSNTFB4HBA":58},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":15,"categories":16,"keywords":17,"publishedAt":31,"linkOnly":32,"markdownContent":33,"markdownBody":34,"frontMatter":35,"refName":37,"commitSha":38,"skillDirectory":39,"forksCount":40,"repositoryHtmlUrl":41,"repositoryHomepageUrl":6,"ownerHtmlUrl":42,"ownerBlogUrl":6,"ownerDescription":6,"updatedAt":43,"install":44,"resources":57},"01M6MX322TD9HQPCYSNTFB4HBA","topsrv",null,"topsrv — метрики, алерты, CVE и HTTP-логи серверов проекта через публичный JSON-RPC API app.topsrv.io. Используй, когда нужна загрузка CPU, памяти или диска хоста, метрики PostgreSQL\u002Fnginx\u002Fangie агента topsrv (`topsrv_*`), какие алерты горят, уязвимые пакеты на серверах или кто (боты, сети, IP) нагружает сайт.","vmkteam\u002Fclaude-plugins","vmkteam","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F55537877?v=4","01M6MWYAFDCD63N1M8MBJA6T0S","01M6MWYAKHK0QCPR0Y7AW5SH0R",7,"MIT","https:\u002F\u002Fgithub.com\u002Fvmkteam\u002Fclaude-plugins\u002Fblob\u002Fmaster\u002Fplugins\u002Fdeveloper\u002Fskills\u002Ftopsrv\u002FSKILL.md",[],[5,18,19,20,21,22,23,24,25,26,27,28,29,30],"http","postgresql","rpc","cve","nginx","api","Bash","json","ip","app","io","CPU","json-rpc","2026-11-10 10:12:30",false,"---\nname: topsrv\ndescription: \"topsrv — метрики, алерты, CVE и HTTP-логи серверов проекта через публичный JSON-RPC API app.topsrv.io. Используй, когда нужна загрузка CPU, памяти или диска хоста, метрики PostgreSQL\u002Fnginx\u002Fangie агента topsrv (`topsrv_*`), какие алерты горят, уязвимые пакеты на серверах или кто (боты, сети, IP) нагружает сайт.\"\nallowed-tools: \"Bash(pcurl:*)\"\n---\n\n# topsrv — серверы проекта: метрики, алерты, инвентарь, HTTP-логи\n\ntopsrv — агент мониторинга серверов (один бинарь вместо node_exporter, postgres_exporter, process-exporter, nginx-exporter и smartctl_exporter) и сервис app.topsrv.io, куда агенты отправляют метрики, инвентарь пакетов и access-логи. Скилл работает с публичным API v1: один токен — один проект, только чтение, кроме `alert.silence`.\n\nМетрики `topsrv_*` лежат в хранилище topsrv, а не в Prometheus проекта: метрики сервиса (`app_*`) — через `\u002Fprometheus`, метрики серверов — здесь.\n\nПрофиль определяется при онбординге (`\u002Fonboard`) и хранится в `~\u002F.claude\u002Fmemory\u002Finfra-{group}.md`, секция topsrv.\n\n## Подключение\n\n```\nProfile:  @{topsrv_profile}                               # обычно topsrv-{slug}; второй токен проекта — topsrv-{slug}-2\nEndpoint: POST https:\u002F\u002Fapp.topsrv.io\u002Fapi\u002Fv1\u002Frpc\u002F          # JSON-RPC 2.0, слэш в конце обязателен\nContract: https:\u002F\u002Fapp.topsrv.io\u002Fapi\u002Fv1\u002Frpc\u002Fopenrpc.json   # открыт без токена\n```\n\n- Профиля нет в infra-файле — найди строки `topsrv-*` в `pcurl show`; если проектов несколько, спроси, какой нужен.\n- Профиля нет вообще — попроси пользователя выпустить токен в app.topsrv.io → Project settings → Tokens → API tokens и выполнить команду `pcurl add` с вкладки pcurl. Токен (`tsk_…`) не спрашивай и не печатай.\n- Проект берётся из токена, ни один метод не принимает `slug`.\n- Сессионный ключ веб-интерфейса (заголовок `Authorization2`) здесь даёт 401 `invalid_token`, а API-токен не работает на внутреннем `\u002Fv1\u002Frpc\u002F`.\n\n## Вызов\n\n```bash\npcurl @{topsrv_profile} -sS https:\u002F\u002Fapp.topsrv.io\u002Fapi\u002Fv1\u002Frpc\u002F \\\n  -d '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"meta.whoami\"}'\n```\n\n- `id` обязателен: тело без него — notification, ответ — пустой 200.\n- Параметры — объектом по именам: `\"params\":{\"hostname\":\"web-1\"}`. Имена методов регистронезависимы.\n- Кавычки PromQL внутри single-quoted тела экранируй `\\\"`. Тело с вычисляемыми значениями (окно времени, длинный PromQL) собирай через `jq -nc` в аргумент `-d \"$(…)\"`: pcurl не передаёт stdin, и `-d @-` даёт `-32700 Parse error`.\n- Профиль хранит `Content-Type: application\u002Fjson`. Если заголовка в профиле нет — добавь `-H 'Content-Type: application\u002Fjson'`, иначе 415.\n- Батч — массив до 10 вызовов, каждый вызов батча стоит единицу rate limit.\n\nПервый вызов в сессии — `meta.whoami`: `project`, `scopes`, `expiresAt`. Метод вне scopes токена отвечает 403 `insufficient_scope` — сообщи пользователю, какой scope нужен. Если до `expiresAt` меньше недели — предупреди.\n\n## Методы\n\n| Метод | Scope | Параметры | Возвращает |\n|-------|-------|-----------|------------|\n| `meta.whoami` | — | — | `project`, `scopes`, `tokenPrefix`, `expiresAt` |\n| `host.list` | hosts:read | — | хосты с метриками за 48 ч: `hostname`, ОС, ядро, `agentVersion`, `lastSeen`, `addresses[]` |\n| `host.get` | hosts:read | `hostname` | один хост; 404 `host_not_found` |\n| `host.summary` | hosts:read | — | строка на хост: `cpu` %, `memUsed`\u002F`memTotal` байт, `diskMax` %, `load` 1m, `uptime` с |\n| `metric.query` | metrics:read | `query` | instant PromQL |\n| `metric.queryRange` | metrics:read | `query`, `start`, `end`, `step`? | range PromQL |\n| `alert.rules` | alerts:read | — | действующие правила (системные и свои, с override); у открытых — `currentState`, `activeEventId` |\n| `alert.list` | alerts:read | `state`?, `severity`?, `host`?, `page`?, `pageSize`? (≤500) | события алертов, новые первыми |\n| `alert.silence` | alerts:write | `input`: `matcher`, `startsAt`, `endsAt`, `comment` | созданное окно тишины |\n| `inventory.hosts` | inventory:read | — | ОС, ядро, пакеты, `cveCount`\u002F`fixableCount`, `rebootRequired`, `lastAuditAt` |\n| `inventory.findings` | inventory:read | `hostname` | открытые уязвимости хоста: пакет, версия, `fixedInVersion`, advisory, CVE |\n| `botlog.search` | botlogs:read | `filter`, `limit`? (≤1000), `offset`? | события ботов |\n| `weblog.summary` | weblogs:read | `filter` | число запросов, avg\u002Fp50\u002Fp95 мс, ответы по классам |\n| `weblog.top` | weblogs:read | `groupBy`, `filter`, `sortBy`?, `sortDir`?, `limit`? (≤500), `offset`?, `details`? | трафик, сгруппированный по измерению |\n| `weblog.networks` | weblogs:read | `filter`, `sortBy`?, `minIPs`?, `limit`? (≤200) | строка на автономную систему |\n| `weblog.blockImpact` | weblogs:read | `ip`?, `userAgent`?, `window`? | кого заденет блокировка IP, UA, подсети \u002F24, AS |\n| `weblog.search` | weblogs:read | `filter`, `sortDir`?, `limit`? (≤500), `offset`? (≤10000) | сырые события HTTP-потока |\n\n`?` — необязательный параметр. Namespace `inventory`, `botlog`, `weblog` регистрируются, только если у инсталляции включён ClickHouse; иначе вызов отвечает `-32601 Method not found`.\n\nПолная схема метода, описания полей и коды ошибок — в контракте; читай его, если нужного поля нет в таблице:\n\n```bash\npcurl @{topsrv_profile} https:\u002F\u002Fapp.topsrv.io\u002Fapi\u002Fv1\u002Frpc\u002Fopenrpc.json -s | jq '.methods[] | select(.name | ascii_downcase == \"weblog.top\")'\npcurl @{topsrv_profile} https:\u002F\u002Fapp.topsrv.io\u002Fapi\u002Fv1\u002Frpc\u002Fopenrpc.json -s | jq '.components.schemas.WebLogFilter'\n```\n\n## Метрики (PromQL)\n\n- Хост — лейбл `instance`, его значение = `hostname` из `host.list`. Лейбл `hostname` есть только у `topsrv_host_info`: `{hostname=\"web-1\"}` у остальных метрик ничего не находит.\n- Ответ — `result.data.result[]`: `metric` — массив пар `{name, value}` (не map), `values[]` — точки `{timestamp, value}`, у instant-запроса ровно одна. Значение — строка, бывает `NaN`, `+Inf`.\n- `queryRange`: `start` и `end` — unix-секунды, обязательны. Без `step` окно делится примерно на 1000 точек, шаг не меньше 1m. Больше 11000 точек — 400 `too_many_points`, увеличь `step`.\n- Лейбла `project_id` в ответе нет.\n- Пример `topsrv_disk_used_percent` из описания `metric.query` в контракте — такой метрики у агента нет, запрос вернёт пустой результат. Заполненность диска считается из `topsrv_filesystem_bytes` (рецепт ниже).\n\nКаталог метрик агента и готовые рецепты — в репозитории агента; читай, когда нужной метрики нет в таблице:\n\n```bash\npcurl https:\u002F\u002Fraw.githubusercontent.com\u002Fvmkteam\u002Ftopsrv\u002Fmaster\u002Fdocs\u002Fmetrics.md -s\npcurl https:\u002F\u002Fraw.githubusercontent.com\u002Fvmkteam\u002Ftopsrv\u002Fmaster\u002Fdocs\u002Fpromql-recipes.md -s\n```\n\n### Основные семейства\n\n| Семейство | Метрики | Лейблы |\n|-----------|---------|--------|\n| Система | `topsrv_cpu_seconds_total`, `topsrv_cpu_cores`, `topsrv_memory_bytes`, `topsrv_load_average`, `topsrv_swap_bytes`, `topsrv_uptime_seconds` | cpu, mode; type (total\u002Fused\u002Favailable\u002F…); interval (1m\u002F5m\u002F15m) |\n| Диск | `topsrv_filesystem_bytes`, `topsrv_filesystem_inodes`, `topsrv_disk_io_time_seconds_total`, `topsrv_disk_read_bytes_total`, `topsrv_disk_write_bytes_total` | mountpoint, device, fstype, type (total\u002Fused\u002Ffree) |\n| Сеть | `topsrv_network_bytes_total`, `topsrv_network_errors_total`, `topsrv_netstat_tcp_connections`, `topsrv_netstat_tcp_retransmits_total`, `topsrv_netstat_listening_ports` | interface, direction; state, direction, remote_scope; proto, port, scope, process |\n| Процессы | `topsrv_process_cpu_seconds_total`, `topsrv_process_memory_bytes`, `topsrv_process_open_fds`, `topsrv_process_worst_fd_ratio` | group; type (rss\u002Fvms\u002Fswap) |\n| PostgreSQL | `topsrv_pg_up`, `topsrv_pg_connections`, `topsrv_pg_max_connections`, `topsrv_pg_longest_transaction_seconds`, `topsrv_pg_blocked_backends`, `topsrv_pg_replication_lag_seconds`, `topsrv_pg_query_time_seconds_total`, `topsrv_pg_table_bloat_pct`, `topsrv_pg_wraparound_xid_age` | state; database, usename; client_addr; queryid, query; schema, table |\n| Nginx | `topsrv_nginx_http_requests_total`, `topsrv_nginx_5xx_requests_total`, `topsrv_nginx_request_duration_seconds`, `topsrv_nginx_upstream_duration_seconds` | status, uri, ExtraLabels из конфига агента (например server_name) |\n| Angie | `topsrv_angie_server_zone_responses_total`, `topsrv_angie_upstream_peer_state`, `topsrv_angie_upstream_peer_responses_total` | zone, code; upstream, peer |\n| S.M.A.R.T. | `topsrv_smart_device_healthy`, `topsrv_smart_device_temperature_celsius`, `topsrv_smart_nvme_percentage_used` | device |\n| SSL | `topsrv_ssl_certificate_expiry_seconds`, `topsrv_ssl_certificate_san_info` | path, cn, issuer; domain |\n| Агент | `topsrv_host_info`, `topsrv_collector_scrape_duration_seconds`, `topsrv_collector_scrape_panics_total` | hostname, version; collector |\n\n### Рецепты\n\n```promql\n# CPU, % (так считает host.summary)\nsum by (instance) (rate(topsrv_cpu_seconds_total{mode!=\"idle\"}[2m])) \u002F on(instance) topsrv_cpu_cores * 100\n\n# Память, %\ntopsrv_memory_bytes{type=\"used\"} \u002F on(instance) topsrv_memory_bytes{type=\"total\"} * 100\n\n# Заполненность файловых систем хоста, %\ntopsrv_filesystem_bytes{instance=\"{instance}\",type=\"used\",fstype!~\"tmpfs|squashfs|devtmpfs|autofs\"}\n  \u002F on(instance, mountpoint, device, fstype) topsrv_filesystem_bytes{type=\"total\"} * 100\n\n# Top-5 групп процессов по CPU, ядер\ntopk(5, sum by (instance, group) (rate(topsrv_process_cpu_seconds_total[5m])))\n\n# PostgreSQL: занятость соединений, %; самая длинная транзакция, с; лаг реплик, с\nsum by (instance) (topsrv_pg_connections) \u002F on(instance) topsrv_pg_max_connections * 100\nmax by (instance, database) (topsrv_pg_longest_transaction_seconds)\nmax by (instance, client_addr) (topsrv_pg_replication_lag_seconds)\n\n# PostgreSQL: top-10 запросов по времени выполнения\ntopk(10, sum by (instance, queryid, query) (rate(topsrv_pg_query_time_seconds_total[5m])))\n\n# Nginx: RPS по статусам; p95 времени ответа, с\nsum by (instance, status) (rate(topsrv_nginx_http_requests_total[5m]))\nhistogram_quantile(0.95, sum by (instance, le) (rate(topsrv_nginx_request_duration_seconds_bucket[5m])))\n\n# TCP retransmit, % (норма до 1–2%)\n100 * rate(topsrv_netstat_tcp_retransmits_total[5m]) \u002F rate(topsrv_netstat_tcp_out_segs_total[5m])\n\n# Дней до истечения сертификатов — те, что истекают в ближайшие 14 дней\n(topsrv_ssl_certificate_expiry_seconds - time()) \u002F 86400 \u003C 14\n\n# Порты, открытые наружу; нездоровые диски\ntopsrv_netstat_listening_ports{scope=\"public\"}\ntopsrv_smart_device_healthy == 0\n```\n\n### Вызовы\n\n```bash\n# Instant\npcurl @{topsrv_profile} -sS https:\u002F\u002Fapp.topsrv.io\u002Fapi\u002Fv1\u002Frpc\u002F \\\n  -d '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"metric.query\",\"params\":{\"query\":\"topsrv_load_average{instance=\\\"{instance}\\\",interval=\\\"1m\\\"}\"}}'\n\n# Range за последний час, шаг по умолчанию\npcurl @{topsrv_profile} -sS https:\u002F\u002Fapp.topsrv.io\u002Fapi\u002Fv1\u002Frpc\u002F -d \"$(jq -nc \\\n  --arg q 'rate(topsrv_cpu_seconds_total{instance=\"{instance}\",mode=\"user\"}[5m])' \\\n  '{jsonrpc:\"2.0\",id:1,method:\"metric.queryRange\",params:{query:$q,start:((now|floor)-3600),end:(now|floor)}}')\"\n\n# Компактный вывод: лейблы и последнее значение серии\n… | jq -c '.result.data.result[] | {m: (.metric | map(\"\\(.name)=\\(.value)\") | join(\",\")), v: .values[-1].value}'\n```\n\n## Алерты\n\n```bash\n# Что горит сейчас: правила с открытым событием\npcurl @{topsrv_profile} -sS https:\u002F\u002Fapp.topsrv.io\u002Fapi\u002Fv1\u002Frpc\u002F -d '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"alert.rules\"}' \\\n  | jq -c '.result[] | select(.currentState) | {code, severity, currentState, activeEventId, promql, threshold}'\n\n# События: firing на хостах, имя которых содержит \"web\"\npcurl @{topsrv_profile} -sS https:\u002F\u002Fapp.topsrv.io\u002Fapi\u002Fv1\u002Frpc\u002F \\\n  -d '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"alert.list\",\"params\":{\"state\":\"firing\",\"host\":\"web\",\"pageSize\":50}}'\n```\n\n- `host` в `alert.list` — подстрока имени хоста. Хост события — в `labels[]`, элемент с `name` = `instance`.\n- Закрытые вручную (dismissed) события `alert.list` не возвращает.\n- `alert.rules[].promql` — выражение правила: прогони его через `metric.queryRange`, чтобы увидеть, как метрика подошла к порогу `threshold`.\n- `alert.silence` — запись. Вызывай только по явной просьбе пользователя, показав matcher и окно до вызова. Matcher: `ruleCode`, `severity`, `instance`, `group` — хотя бы одно поле; `ruleCode` и `group` берутся из `alert.rules`. Окно — RFC3339, не длиннее 30 дней.\n\n## Инвентарь и CVE\n\n```bash\n# Хосты с исправимыми уязвимостями\npcurl @{topsrv_profile} -sS https:\u002F\u002Fapp.topsrv.io\u002Fapi\u002Fv1\u002Frpc\u002F -d '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"inventory.hosts\"}' \\\n  | jq -c '.result[] | select(.fixableCount > 0) | {hostname, os, osVersion, cveCount, fixableCount, rebootRequired, lastAuditAt}'\n\n# Исправимые находки одного хоста\npcurl @{topsrv_profile} -sS https:\u002F\u002Fapp.topsrv.io\u002Fapi\u002Fv1\u002Frpc\u002F \\\n  -d '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"inventory.findings\",\"params\":{\"hostname\":\"{instance}\"}}' \\\n  | jq -c '.result[] | select(.fixedInVersion != \"\") | {package, version, fixedInVersion, cveId}'\n```\n\n- `cveCount: 0` сам по себе ничего не значит: без `lastAuditAt` аудит ещё не запускался.\n- `rebootRequired` — установлено ядро новее работающего, фикс ядра вступит в силу после перезагрузки.\n- Пустой `fixedInVersion` — исправленной версии пакета ещё нет.\n\n## HTTP-логи: botlog и weblog\n\n| | botlog | weblog |\n|-|--------|--------|\n| Что | только запросы ботов (UA из каталога агента) | весь HTTP-поток, включая людей |\n| Хранение | 90 дней | 72 часа — это и максимальное окно запроса |\n| Методы | `search` | `summary`, `top`, `networks`, `blockImpact`, `search` |\n\n- `filter` обязателен: `{}` — последние 24 часа. Время — RFC3339; `from` включительно, `to` строго.\n- Фильтры weblog — массивы: `host`, `serverName`, `path`, `pathTopSection`, `method`, `status`, `statusClass` (`2xx`…`5xx`, `dropped` — 444, 499 и коды выше 599), `remoteAddr` (IP или IPv4 CIDR), `asn`, `asnOrg`, `networkType`, `country`, `userAgent`, `botName`, `verifyState`, `requestId`, `visitorId`; скаляры `uaMatched`, `ipListed`, `requestTimeMinMs`.\n- `weblog.top`: `groupBy` — host | hostPath | path | pathTopSection | remoteAddr | asn | userAgent | country | status | platform | appVersion | platformVersion | visitorId; `sortBy` — requests | bytes | effort | avgMs | p95Ms | c4xx | c5xx | firstSeen | lastSeen. `effort` — суммарное время обслуживания: сколько группа стоит бэкенду.\n- Прежде чем советовать блокировку IP, UA или подсети — `weblog.blockImpact`: `visitorsOnlyHere` показывает, сколько живых посетителей потеряют доступ.\n- `weblog.search` выгружай по времени, а не по offset: `sortDir: \"asc\"`, сдвигай `from` на время последнего события, дубли отбрасывай по `requestId`.\n- `requestId` — id запроса, который nginx передал бэкенду: по нему событие находится в логах сервиса (`\u002Floki`).\n- `botlog.search` с `limit` больше 1000 отвечает 400, а не урезанной страницей. `verifyState: spoofed` — UA бота, но IP не подтверждает бота.\n- Ответы: `weblog.top` — `result.rows[]`, значение группы в `value`, стоимость группы в `effortMs`; `weblog.search` — `result.events[]`, адрес с query string в `uri` (поля `path` у события нет), время — `requestTimeMs` и `upstreamMs`.\n- Логи пишутся с момента, когда в агенте включили WebLogs, и окно бывает короче 72 часов. Первый срез — `weblog.top` с `groupBy: \"host\"` и `from` на 71 час назад (ровно 72 часа сервер считает как `72h1m0s` и отвечает 400): `firstSeen` покажет начало данных, строки — сайты проекта. Если сайтов несколько, фильтруй по `host`.\n- Независимые срезы (summary, top по разным измерениям) отправляй одним батчем.\n- Как читать 5xx: 502 за единицы миллисекунд при `upstreamMs: 0` — бэкенд не отвечал, обычно это рестарт при выкатке (сверь время с деплоями); 503\u002F504 с `upstreamMs`, равным таймауту, — бэкенд не успел ответить.\n- 499 и 405 на стриминговых эндпоинтах (MCP, SSE) — норма протокола: клиент закрыл поток, GET без поддержки SSE. Это не ошибки сайта.\n- Живые посетители — примерно `uaMatched: false` вместе с `networkType: [\"residential\",\"mobile\"]`.\n\n```bash\n# Кто нагружает сайт за последний час: AS по суммарному времени обслуживания\npcurl @{topsrv_profile} -sS https:\u002F\u002Fapp.topsrv.io\u002Fapi\u002Fv1\u002Frpc\u002F -d \"$(jq -nc \\\n  '{jsonrpc:\"2.0\",id:1,method:\"weblog.top\",params:{groupBy:\"asn\",sortBy:\"effort\",filter:{from:(now-3600|todate)},limit:10}}')\"\n\n# Самые дорогие пути с 5xx за последний час\npcurl @{topsrv_profile} -sS https:\u002F\u002Fapp.topsrv.io\u002Fapi\u002Fv1\u002Frpc\u002F -d \"$(jq -nc \\\n  '{jsonrpc:\"2.0\",id:1,method:\"weblog.top\",params:{groupBy:\"path\",sortBy:\"effort\",filter:{from:(now-3600|todate),statusClass:[\"5xx\"]},limit:20}}')\"\n\n# Пулы скраперов: сети с десятками IP и малым числом User-Agent (uaPerIp сортирует по возрастанию)\npcurl @{topsrv_profile} -sS https:\u002F\u002Fapp.topsrv.io\u002Fapi\u002Fv1\u002Frpc\u002F \\\n  -d '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"weblog.networks\",\"params\":{\"filter\":{},\"minIPs\":20,\"sortBy\":\"uaPerIp\",\"limit\":20}}'\n\n# Кто запрашивает файл (llms.txt, robots.txt) и с каким ответом\npcurl @{topsrv_profile} -sS https:\u002F\u002Fapp.topsrv.io\u002Fapi\u002Fv1\u002Frpc\u002F \\\n  -d '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"weblog.top\",\"params\":{\"groupBy\":\"userAgent\",\"filter\":{\"path\":[\"\u002Fllms.txt\"]},\"limit\":50}}'\n\n# Поддельные боты за сутки\npcurl @{topsrv_profile} -sS https:\u002F\u002Fapp.topsrv.io\u002Fapi\u002Fv1\u002Frpc\u002F \\\n  -d '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"botlog.search\",\"params\":{\"filter\":{\"verifyState\":[\"spoofed\"]},\"limit\":100}}'\n```\n\n## Ошибки\n\nКод и текст — в `error.code` и `error.message` ответа.\n\n| Код | message | Что делать |\n|-----|---------|------------|\n| пустой 200 | — | в запросе нет `id` |\n| 401 | `invalid_token`, `token_expired`, `token_revoked` | пользователь перевыпускает токен в UI и обновляет профиль; `pcurl add` сам не запускай |\n| 403 | `insufficient_scope` | у токена нет scope метода; токен при этом валиден |\n| 400 | `bad_query` | PromQL отклонён VictoriaMetrics, причина — в `error.data` |\n| 400 | `invalid_range`, `too_many_points` | нет `start`\u002F`end` или `end` не позже `start`; увеличь `step` |\n| 400 | `period exceeds 72h…` | окно weblog шире 72 часов |\n| 404 | `host_not_found` | хоста нет среди живых за 48 ч — сверь с `host.list` |\n| 415 | Unsupported Media Type | в профиле нет `Content-Type: application\u002Fjson` |\n| 429 | `rate_limited` | 60 запросов в минуту на токен, burst 20; подожди `Retry-After` |\n| -32601 | Method not found | опечатка в имени метода или namespace не зарегистрирован |\n| -32700 | Parse error | тело не JSON, например `-d @-` |\n\n## Прямые ссылки\n\n```\nhttps:\u002F\u002Fapp.topsrv.io\u002Fprojects\u002F{slug}\u002F                 # хосты\nhttps:\u002F\u002Fapp.topsrv.io\u002Fprojects\u002F{slug}\u002F{instance}\u002F      # хост\nhttps:\u002F\u002Fapp.topsrv.io\u002Fprojects\u002F{slug}\u002Falerts\u002F\nhttps:\u002F\u002Fapp.topsrv.io\u002Fprojects\u002F{slug}\u002Finventory\u002F\nhttps:\u002F\u002Fapp.topsrv.io\u002Fprojects\u002F{slug}\u002Fweb-logs\u002F\nhttps:\u002F\u002Fapp.topsrv.io\u002Fprojects\u002F{slug}\u002Fbot-logs\u002F\n```\n","# topsrv — серверы проекта: метрики, алерты, инвентарь, HTTP-логи\n\ntopsrv — агент мониторинга серверов (один бинарь вместо node_exporter, postgres_exporter, process-exporter, nginx-exporter и smartctl_exporter) и сервис app.topsrv.io, куда агенты отправляют метрики, инвентарь пакетов и access-логи. Скилл работает с публичным API v1: один токен — один проект, только чтение, кроме `alert.silence`.\n\nМетрики `topsrv_*` лежат в хранилище topsrv, а не в Prometheus проекта: метрики сервиса (`app_*`) — через `\u002Fprometheus`, метрики серверов — здесь.\n\nПрофиль определяется при онбординге (`\u002Fonboard`) и хранится в `~\u002F.claude\u002Fmemory\u002Finfra-{group}.md`, секция topsrv.\n\n## Подключение\n\n```\nProfile:  @{topsrv_profile}                               # обычно topsrv-{slug}; второй токен проекта — topsrv-{slug}-2\nEndpoint: POST https:\u002F\u002Fapp.topsrv.io\u002Fapi\u002Fv1\u002Frpc\u002F          # JSON-RPC 2.0, слэш в конце обязателен\nContract: https:\u002F\u002Fapp.topsrv.io\u002Fapi\u002Fv1\u002Frpc\u002Fopenrpc.json   # открыт без токена\n```\n\n- Профиля нет в infra-файле — найди строки `topsrv-*` в `pcurl show`; если проектов несколько, спроси, какой нужен.\n- Профиля нет вообще — попроси пользователя выпустить токен в app.topsrv.io → Project settings → Tokens → API tokens и выполнить команду `pcurl add` с вкладки pcurl. Токен (`tsk_…`) не спрашивай и не печатай.\n- Проект берётся из токена, ни один метод не принимает `slug`.\n- Сессионный ключ веб-интерфейса (заголовок `Authorization2`) здесь даёт 401 `invalid_token`, а API-токен не работает на внутреннем `\u002Fv1\u002Frpc\u002F`.\n\n## Вызов\n\n```bash\npcurl @{topsrv_profile} -sS https:\u002F\u002Fapp.topsrv.io\u002Fapi\u002Fv1\u002Frpc\u002F \\\n  -d '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"meta.whoami\"}'\n```\n\n- `id` обязателен: тело без него — notification, ответ — пустой 200.\n- Параметры — объектом по именам: `\"params\":{\"hostname\":\"web-1\"}`. Имена методов регистронезависимы.\n- Кавычки PromQL внутри single-quoted тела экранируй `\\\"`. Тело с вычисляемыми значениями (окно времени, длинный PromQL) собирай через `jq -nc` в аргумент `-d \"$(…)\"`: pcurl не передаёт stdin, и `-d @-` даёт `-32700 Parse error`.\n- Профиль хранит `Content-Type: application\u002Fjson`. Если заголовка в профиле нет — добавь `-H 'Content-Type: application\u002Fjson'`, иначе 415.\n- Батч — массив до 10 вызовов, каждый вызов батча стоит единицу rate limit.\n\nПервый вызов в сессии — `meta.whoami`: `project`, `scopes`, `expiresAt`. Метод вне scopes токена отвечает 403 `insufficient_scope` — сообщи пользователю, какой scope нужен. Если до `expiresAt` меньше недели — предупреди.\n\n## Методы\n\n| Метод | Scope | Параметры | Возвращает |\n|-------|-------|-----------|------------|\n| `meta.whoami` | — | — | `project`, `scopes`, `tokenPrefix`, `expiresAt` |\n| `host.list` | hosts:read | — | хосты с метриками за 48 ч: `hostname`, ОС, ядро, `agentVersion`, `lastSeen`, `addresses[]` |\n| `host.get` | hosts:read | `hostname` | один хост; 404 `host_not_found` |\n| `host.summary` | hosts:read | — | строка на хост: `cpu` %, `memUsed`\u002F`memTotal` байт, `diskMax` %, `load` 1m, `uptime` с |\n| `metric.query` | metrics:read | `query` | instant PromQL |\n| `metric.queryRange` | metrics:read | `query`, `start`, `end`, `step`? | range PromQL |\n| `alert.rules` | alerts:read | — | действующие правила (системные и свои, с override); у открытых — `currentState`, `activeEventId` |\n| `alert.list` | alerts:read | `state`?, `severity`?, `host`?, `page`?, `pageSize`? (≤500) | события алертов, новые первыми |\n| `alert.silence` | alerts:write | `input`: `matcher`, `startsAt`, `endsAt`, `comment` | созданное окно тишины |\n| `inventory.hosts` | inventory:read | — | ОС, ядро, пакеты, `cveCount`\u002F`fixableCount`, `rebootRequired`, `lastAuditAt` |\n| `inventory.findings` | inventory:read | `hostname` | открытые уязвимости хоста: пакет, версия, `fixedInVersion`, advisory, CVE |\n| `botlog.search` | botlogs:read | `filter`, `limit`? (≤1000), `offset`? | события ботов |\n| `weblog.summary` | weblogs:read | `filter` | число запросов, avg\u002Fp50\u002Fp95 мс, ответы по классам |\n| `weblog.top` | weblogs:read | `groupBy`, `filter`, `sortBy`?, `sortDir`?, `limit`? (≤500), `offset`?, `details`? | трафик, сгруппированный по измерению |\n| `weblog.networks` | weblogs:read | `filter`, `sortBy`?, `minIPs`?, `limit`? (≤200) | строка на автономную систему |\n| `weblog.blockImpact` | weblogs:read | `ip`?, `userAgent`?, `window`? | кого заденет блокировка IP, UA, подсети \u002F24, AS |\n| `weblog.search` | weblogs:read | `filter`, `sortDir`?, `limit`? (≤500), `offset`? (≤10000) | сырые события HTTP-потока |\n\n`?` — необязательный параметр. Namespace `inventory`, `botlog`, `weblog` регистрируются, только если у инсталляции включён ClickHouse; иначе вызов отвечает `-32601 Method not found`.\n\nПолная схема метода, описания полей и коды ошибок — в контракте; читай его, если нужного поля нет в таблице:\n\n```bash\npcurl @{topsrv_profile} https:\u002F\u002Fapp.topsrv.io\u002Fapi\u002Fv1\u002Frpc\u002Fopenrpc.json -s | jq '.methods[] | select(.name | ascii_downcase == \"weblog.top\")'\npcurl @{topsrv_profile} https:\u002F\u002Fapp.topsrv.io\u002Fapi\u002Fv1\u002Frpc\u002Fopenrpc.json -s | jq '.components.schemas.WebLogFilter'\n```\n\n## Метрики (PromQL)\n\n- Хост — лейбл `instance`, его значение = `hostname` из `host.list`. Лейбл `hostname` есть только у `topsrv_host_info`: `{hostname=\"web-1\"}` у остальных метрик ничего не находит.\n- Ответ — `result.data.result[]`: `metric` — массив пар `{name, value}` (не map), `values[]` — точки `{timestamp, value}`, у instant-запроса ровно одна. Значение — строка, бывает `NaN`, `+Inf`.\n- `queryRange`: `start` и `end` — unix-секунды, обязательны. Без `step` окно делится примерно на 1000 точек, шаг не меньше 1m. Больше 11000 точек — 400 `too_many_points`, увеличь `step`.\n- Лейбла `project_id` в ответе нет.\n- Пример `topsrv_disk_used_percent` из описания `metric.query` в контракте — такой метрики у агента нет, запрос вернёт пустой результат. Заполненность диска считается из `topsrv_filesystem_bytes` (рецепт ниже).\n\nКаталог метрик агента и готовые рецепты — в репозитории агента; читай, когда нужной метрики нет в таблице:\n\n```bash\npcurl https:\u002F\u002Fraw.githubusercontent.com\u002Fvmkteam\u002Ftopsrv\u002Fmaster\u002Fdocs\u002Fmetrics.md -s\npcurl https:\u002F\u002Fraw.githubusercontent.com\u002Fvmkteam\u002Ftopsrv\u002Fmaster\u002Fdocs\u002Fpromql-recipes.md -s\n```\n\n### Основные семейства\n\n| Семейство | Метрики | Лейблы |\n|-----------|---------|--------|\n| Система | `topsrv_cpu_seconds_total`, `topsrv_cpu_cores`, `topsrv_memory_bytes`, `topsrv_load_average`, `topsrv_swap_bytes`, `topsrv_uptime_seconds` | cpu, mode; type (total\u002Fused\u002Favailable\u002F…); interval (1m\u002F5m\u002F15m) |\n| Диск | `topsrv_filesystem_bytes`, `topsrv_filesystem_inodes`, `topsrv_disk_io_time_seconds_total`, `topsrv_disk_read_bytes_total`, `topsrv_disk_write_bytes_total` | mountpoint, device, fstype, type (total\u002Fused\u002Ffree) |\n| Сеть | `topsrv_network_bytes_total`, `topsrv_network_errors_total`, `topsrv_netstat_tcp_connections`, `topsrv_netstat_tcp_retransmits_total`, `topsrv_netstat_listening_ports` | interface, direction; state, direction, remote_scope; proto, port, scope, process |\n| Процессы | `topsrv_process_cpu_seconds_total`, `topsrv_process_memory_bytes`, `topsrv_process_open_fds`, `topsrv_process_worst_fd_ratio` | group; type (rss\u002Fvms\u002Fswap) |\n| PostgreSQL | `topsrv_pg_up`, `topsrv_pg_connections`, `topsrv_pg_max_connections`, `topsrv_pg_longest_transaction_seconds`, `topsrv_pg_blocked_backends`, `topsrv_pg_replication_lag_seconds`, `topsrv_pg_query_time_seconds_total`, `topsrv_pg_table_bloat_pct`, `topsrv_pg_wraparound_xid_age` | state; database, usename; client_addr; queryid, query; schema, table |\n| Nginx | `topsrv_nginx_http_requests_total`, `topsrv_nginx_5xx_requests_total`, `topsrv_nginx_request_duration_seconds`, `topsrv_nginx_upstream_duration_seconds` | status, uri, ExtraLabels из конфига агента (например server_name) |\n| Angie | `topsrv_angie_server_zone_responses_total`, `topsrv_angie_upstream_peer_state`, `topsrv_angie_upstream_peer_responses_total` | zone, code; upstream, peer |\n| S.M.A.R.T. | `topsrv_smart_device_healthy`, `topsrv_smart_device_temperature_celsius`, `topsrv_smart_nvme_percentage_used` | device |\n| SSL | `topsrv_ssl_certificate_expiry_seconds`, `topsrv_ssl_certificate_san_info` | path, cn, issuer; domain |\n| Агент | `topsrv_host_info`, `topsrv_collector_scrape_duration_seconds`, `topsrv_collector_scrape_panics_total` | hostname, version; collector |\n\n### Рецепты\n\n```promql\n# CPU, % (так считает host.summary)\nsum by (instance) (rate(topsrv_cpu_seconds_total{mode!=\"idle\"}[2m])) \u002F on(instance) topsrv_cpu_cores * 100\n\n# Память, %\ntopsrv_memory_bytes{type=\"used\"} \u002F on(instance) topsrv_memory_bytes{type=\"total\"} * 100\n\n# Заполненность файловых систем хоста, %\ntopsrv_filesystem_bytes{instance=\"{instance}\",type=\"used\",fstype!~\"tmpfs|squashfs|devtmpfs|autofs\"}\n  \u002F on(instance, mountpoint, device, fstype) topsrv_filesystem_bytes{type=\"total\"} * 100\n\n# Top-5 групп процессов по CPU, ядер\ntopk(5, sum by (instance, group) (rate(topsrv_process_cpu_seconds_total[5m])))\n\n# PostgreSQL: занятость соединений, %; самая длинная транзакция, с; лаг реплик, с\nsum by (instance) (topsrv_pg_connections) \u002F on(instance) topsrv_pg_max_connections * 100\nmax by (instance, database) (topsrv_pg_longest_transaction_seconds)\nmax by (instance, client_addr) (topsrv_pg_replication_lag_seconds)\n\n# PostgreSQL: top-10 запросов по времени выполнения\ntopk(10, sum by (instance, queryid, query) (rate(topsrv_pg_query_time_seconds_total[5m])))\n\n# Nginx: RPS по статусам; p95 времени ответа, с\nsum by (instance, status) (rate(topsrv_nginx_http_requests_total[5m]))\nhistogram_quantile(0.95, sum by (instance, le) (rate(topsrv_nginx_request_duration_seconds_bucket[5m])))\n\n# TCP retransmit, % (норма до 1–2%)\n100 * rate(topsrv_netstat_tcp_retransmits_total[5m]) \u002F rate(topsrv_netstat_tcp_out_segs_total[5m])\n\n# Дней до истечения сертификатов — те, что истекают в ближайшие 14 дней\n(topsrv_ssl_certificate_expiry_seconds - time()) \u002F 86400 \u003C 14\n\n# Порты, открытые наружу; нездоровые диски\ntopsrv_netstat_listening_ports{scope=\"public\"}\ntopsrv_smart_device_healthy == 0\n```\n\n### Вызовы\n\n```bash\n# Instant\npcurl @{topsrv_profile} -sS https:\u002F\u002Fapp.topsrv.io\u002Fapi\u002Fv1\u002Frpc\u002F \\\n  -d '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"metric.query\",\"params\":{\"query\":\"topsrv_load_average{instance=\\\"{instance}\\\",interval=\\\"1m\\\"}\"}}'\n\n# Range за последний час, шаг по умолчанию\npcurl @{topsrv_profile} -sS https:\u002F\u002Fapp.topsrv.io\u002Fapi\u002Fv1\u002Frpc\u002F -d \"$(jq -nc \\\n  --arg q 'rate(topsrv_cpu_seconds_total{instance=\"{instance}\",mode=\"user\"}[5m])' \\\n  '{jsonrpc:\"2.0\",id:1,method:\"metric.queryRange\",params:{query:$q,start:((now|floor)-3600),end:(now|floor)}}')\"\n\n# Компактный вывод: лейблы и последнее значение серии\n… | jq -c '.result.data.result[] | {m: (.metric | map(\"\\(.name)=\\(.value)\") | join(\",\")), v: .values[-1].value}'\n```\n\n## Алерты\n\n```bash\n# Что горит сейчас: правила с открытым событием\npcurl @{topsrv_profile} -sS https:\u002F\u002Fapp.topsrv.io\u002Fapi\u002Fv1\u002Frpc\u002F -d '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"alert.rules\"}' \\\n  | jq -c '.result[] | select(.currentState) | {code, severity, currentState, activeEventId, promql, threshold}'\n\n# События: firing на хостах, имя которых содержит \"web\"\npcurl @{topsrv_profile} -sS https:\u002F\u002Fapp.topsrv.io\u002Fapi\u002Fv1\u002Frpc\u002F \\\n  -d '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"alert.list\",\"params\":{\"state\":\"firing\",\"host\":\"web\",\"pageSize\":50}}'\n```\n\n- `host` в `alert.list` — подстрока имени хоста. Хост события — в `labels[]`, элемент с `name` = `instance`.\n- Закрытые вручную (dismissed) события `alert.list` не возвращает.\n- `alert.rules[].promql` — выражение правила: прогони его через `metric.queryRange`, чтобы увидеть, как метрика подошла к порогу `threshold`.\n- `alert.silence` — запись. Вызывай только по явной просьбе пользователя, показав matcher и окно до вызова. Matcher: `ruleCode`, `severity`, `instance`, `group` — хотя бы одно поле; `ruleCode` и `group` берутся из `alert.rules`. Окно — RFC3339, не длиннее 30 дней.\n\n## Инвентарь и CVE\n\n```bash\n# Хосты с исправимыми уязвимостями\npcurl @{topsrv_profile} -sS https:\u002F\u002Fapp.topsrv.io\u002Fapi\u002Fv1\u002Frpc\u002F -d '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"inventory.hosts\"}' \\\n  | jq -c '.result[] | select(.fixableCount > 0) | {hostname, os, osVersion, cveCount, fixableCount, rebootRequired, lastAuditAt}'\n\n# Исправимые находки одного хоста\npcurl @{topsrv_profile} -sS https:\u002F\u002Fapp.topsrv.io\u002Fapi\u002Fv1\u002Frpc\u002F \\\n  -d '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"inventory.findings\",\"params\":{\"hostname\":\"{instance}\"}}' \\\n  | jq -c '.result[] | select(.fixedInVersion != \"\") | {package, version, fixedInVersion, cveId}'\n```\n\n- `cveCount: 0` сам по себе ничего не значит: без `lastAuditAt` аудит ещё не запускался.\n- `rebootRequired` — установлено ядро новее работающего, фикс ядра вступит в силу после перезагрузки.\n- Пустой `fixedInVersion` — исправленной версии пакета ещё нет.\n\n## HTTP-логи: botlog и weblog\n\n| | botlog | weblog |\n|-|--------|--------|\n| Что | только запросы ботов (UA из каталога агента) | весь HTTP-поток, включая людей |\n| Хранение | 90 дней | 72 часа — это и максимальное окно запроса |\n| Методы | `search` | `summary`, `top`, `networks`, `blockImpact`, `search` |\n\n- `filter` обязателен: `{}` — последние 24 часа. Время — RFC3339; `from` включительно, `to` строго.\n- Фильтры weblog — массивы: `host`, `serverName`, `path`, `pathTopSection`, `method`, `status`, `statusClass` (`2xx`…`5xx`, `dropped` — 444, 499 и коды выше 599), `remoteAddr` (IP или IPv4 CIDR), `asn`, `asnOrg`, `networkType`, `country`, `userAgent`, `botName`, `verifyState`, `requestId`, `visitorId`; скаляры `uaMatched`, `ipListed`, `requestTimeMinMs`.\n- `weblog.top`: `groupBy` — host | hostPath | path | pathTopSection | remoteAddr | asn | userAgent | country | status | platform | appVersion | platformVersion | visitorId; `sortBy` — requests | bytes | effort | avgMs | p95Ms | c4xx | c5xx | firstSeen | lastSeen. `effort` — суммарное время обслуживания: сколько группа стоит бэкенду.\n- Прежде чем советовать блокировку IP, UA или подсети — `weblog.blockImpact`: `visitorsOnlyHere` показывает, сколько живых посетителей потеряют доступ.\n- `weblog.search` выгружай по времени, а не по offset: `sortDir: \"asc\"`, сдвигай `from` на время последнего события, дубли отбрасывай по `requestId`.\n- `requestId` — id запроса, который nginx передал бэкенду: по нему событие находится в логах сервиса (`\u002Floki`).\n- `botlog.search` с `limit` больше 1000 отвечает 400, а не урезанной страницей. `verifyState: spoofed` — UA бота, но IP не подтверждает бота.\n- Ответы: `weblog.top` — `result.rows[]`, значение группы в `value`, стоимость группы в `effortMs`; `weblog.search` — `result.events[]`, адрес с query string в `uri` (поля `path` у события нет), время — `requestTimeMs` и `upstreamMs`.\n- Логи пишутся с момента, когда в агенте включили WebLogs, и окно бывает короче 72 часов. Первый срез — `weblog.top` с `groupBy: \"host\"` и `from` на 71 час назад (ровно 72 часа сервер считает как `72h1m0s` и отвечает 400): `firstSeen` покажет начало данных, строки — сайты проекта. Если сайтов несколько, фильтруй по `host`.\n- Независимые срезы (summary, top по разным измерениям) отправляй одним батчем.\n- Как читать 5xx: 502 за единицы миллисекунд при `upstreamMs: 0` — бэкенд не отвечал, обычно это рестарт при выкатке (сверь время с деплоями); 503\u002F504 с `upstreamMs`, равным таймауту, — бэкенд не успел ответить.\n- 499 и 405 на стриминговых эндпоинтах (MCP, SSE) — норма протокола: клиент закрыл поток, GET без поддержки SSE. Это не ошибки сайта.\n- Живые посетители — примерно `uaMatched: false` вместе с `networkType: [\"residential\",\"mobile\"]`.\n\n```bash\n# Кто нагружает сайт за последний час: AS по суммарному времени обслуживания\npcurl @{topsrv_profile} -sS https:\u002F\u002Fapp.topsrv.io\u002Fapi\u002Fv1\u002Frpc\u002F -d \"$(jq -nc \\\n  '{jsonrpc:\"2.0\",id:1,method:\"weblog.top\",params:{groupBy:\"asn\",sortBy:\"effort\",filter:{from:(now-3600|todate)},limit:10}}')\"\n\n# Самые дорогие пути с 5xx за последний час\npcurl @{topsrv_profile} -sS https:\u002F\u002Fapp.topsrv.io\u002Fapi\u002Fv1\u002Frpc\u002F -d \"$(jq -nc \\\n  '{jsonrpc:\"2.0\",id:1,method:\"weblog.top\",params:{groupBy:\"path\",sortBy:\"effort\",filter:{from:(now-3600|todate),statusClass:[\"5xx\"]},limit:20}}')\"\n\n# Пулы скраперов: сети с десятками IP и малым числом User-Agent (uaPerIp сортирует по возрастанию)\npcurl @{topsrv_profile} -sS https:\u002F\u002Fapp.topsrv.io\u002Fapi\u002Fv1\u002Frpc\u002F \\\n  -d '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"weblog.networks\",\"params\":{\"filter\":{},\"minIPs\":20,\"sortBy\":\"uaPerIp\",\"limit\":20}}'\n\n# Кто запрашивает файл (llms.txt, robots.txt) и с каким ответом\npcurl @{topsrv_profile} -sS https:\u002F\u002Fapp.topsrv.io\u002Fapi\u002Fv1\u002Frpc\u002F \\\n  -d '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"weblog.top\",\"params\":{\"groupBy\":\"userAgent\",\"filter\":{\"path\":[\"\u002Fllms.txt\"]},\"limit\":50}}'\n\n# Поддельные боты за сутки\npcurl @{topsrv_profile} -sS https:\u002F\u002Fapp.topsrv.io\u002Fapi\u002Fv1\u002Frpc\u002F \\\n  -d '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"botlog.search\",\"params\":{\"filter\":{\"verifyState\":[\"spoofed\"]},\"limit\":100}}'\n```\n\n## Ошибки\n\nКод и текст — в `error.code` и `error.message` ответа.\n\n| Код | message | Что делать |\n|-----|---------|------------|\n| пустой 200 | — | в запросе нет `id` |\n| 401 | `invalid_token`, `token_expired`, `token_revoked` | пользователь перевыпускает токен в UI и обновляет профиль; `pcurl add` сам не запускай |\n| 403 | `insufficient_scope` | у токена нет scope метода; токен при этом валиден |\n| 400 | `bad_query` | PromQL отклонён VictoriaMetrics, причина — в `error.data` |\n| 400 | `invalid_range`, `too_many_points` | нет `start`\u002F`end` или `end` не позже `start`; увеличь `step` |\n| 400 | `period exceeds 72h…` | окно weblog шире 72 часов |\n| 404 | `host_not_found` | хоста нет среди живых за 48 ч — сверь с `host.list` |\n| 415 | Unsupported Media Type | в профиле нет `Content-Type: application\u002Fjson` |\n| 429 | `rate_limited` | 60 запросов в минуту на токен, burst 20; подожди `Retry-After` |\n| -32601 | Method not found | опечатка в имени метода или namespace не зарегистрирован |\n| -32700 | Parse error | тело не JSON, например `-d @-` |\n\n## Прямые ссылки\n\n```\nhttps:\u002F\u002Fapp.topsrv.io\u002Fprojects\u002F{slug}\u002F                 # хосты\nhttps:\u002F\u002Fapp.topsrv.io\u002Fprojects\u002F{slug}\u002F{instance}\u002F      # хост\nhttps:\u002F\u002Fapp.topsrv.io\u002Fprojects\u002F{slug}\u002Falerts\u002F\nhttps:\u002F\u002Fapp.topsrv.io\u002Fprojects\u002F{slug}\u002Finventory\u002F\nhttps:\u002F\u002Fapp.topsrv.io\u002Fprojects\u002F{slug}\u002Fweb-logs\u002F\nhttps:\u002F\u002Fapp.topsrv.io\u002Fprojects\u002F{slug}\u002Fbot-logs\u002F\n```\n",{"name":5,"description":7,"allowed-tools":36},"Bash(pcurl:*)","master","8f3147ce24a1a470d9aaa02aa775d7797d621d95","plugins\u002Fdeveloper\u002Fskills\u002Ftopsrv",1,"https:\u002F\u002Fgithub.com\u002Fvmkteam\u002Fclaude-plugins","https:\u002F\u002Fgithub.com\u002Fvmkteam","2026-11-05 00:26:25",{"source":8,"skillName":5,"commands":45,"prompt":55,"zipAvailable":56},[46,49,52],{"tool":47,"command":48},"npx","npx skills add vmkteam\u002Fclaude-plugins --skill topsrv",{"tool":50,"command":51},"bunx","bunx skills add vmkteam\u002Fclaude-plugins --skill topsrv",{"tool":53,"command":54},"pnpm","pnpm dlx skills add vmkteam\u002Fclaude-plugins --skill topsrv","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fvmkteam\u002Fclaude-plugins\u002Fblob\u002Fmaster\u002Fplugins\u002Fdeveloper\u002Fskills\u002Ftopsrv\u002FSKILL.md",true,[],[59,67,78,95,107,127],{"skillNo":60,"name":61,"nameZh":6,"description":62,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":63,"categories":64,"keywords":65,"publishedAt":31},"01M6MX3A8MZJBTMMK6WMY35FYH","zenrpc","Zenrpc — справочник по JSON-RPC 2.0 серверу для Go. Используй при создании и редактировании RPC-сервисов.","https:\u002F\u002Fgithub.com\u002Fvmkteam\u002Fclaude-plugins\u002Fblob\u002Fmaster\u002Fplugins\u002Fdeveloper\u002Fskills\u002Fzenrpc\u002FSKILL.md",[],[61,66,20,25,30],"go",{"skillNo":68,"name":69,"nameZh":6,"description":70,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":71,"categories":72,"keywords":73,"publishedAt":31},"01M6MX36PEA0NSHCCRF5GGVVTZ","youtrack","YouTrack — задачи через REST API и pcurl. Используй для поиска issues, создания, обновления описаний, смены assignee\u002Fstage\u002Fприоритета, логирования времени и работы с комментариями.","https:\u002F\u002Fgithub.com\u002Fvmkteam\u002Fclaude-plugins\u002Fblob\u002Fmaster\u002Fplugins\u002Fdeveloper\u002Fskills\u002Fyoutrack\u002FSKILL.md",[],[69,74,75,23,76,77],"REST","issues","stage","rest api",{"skillNo":79,"name":80,"nameZh":6,"description":81,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":82,"categories":83,"keywords":89,"publishedAt":31},"01M6MX2XFXCHNRPPW6WYZ15BXN","testing","Testing — используй всегда, когда пишешь или правишь Go-тесты (*_test.go), в том числе по сценариям Given\u002FWhen\u002FThen из spec: фреймворк как в соседних файлах, префикс TestDB, фабрики pkg\u002Fdb\u002Ftest, реальная БД без моков. Также при настройке TestMain\u002Ftest-short и когда тесты падают в gate, но проходят локально.","https:\u002F\u002Fgithub.com\u002Fvmkteam\u002Fclaude-plugins\u002Fblob\u002Fmaster\u002Fplugins\u002Fdeveloper\u002Fskills\u002Ftesting\u002FSKILL.md",[84],{"categoryNo":85,"categoryName":86,"taxonomyType":87,"taxonomyTypeDesc":88},"01JBSEEDCAT000000000000008","测试与质量","SCENARIO","使用场景",[80,90,66,91,92,93,94],"spec","test","short","gate","db",{"skillNo":96,"name":97,"nameZh":6,"description":98,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":99,"categories":100,"keywords":101,"publishedAt":31},"01M6MX2P8RFYPV4EXRMVW6KAN7","solve","Solve — полный цикл решения задачи из YouTrack: preflight → анализ → план → реализация → ревью → коммит. Используй при любой YouTrack-задаче (`\u002Fsolve PLF-819`); флаг `--fast` сокращает HITL для тривиальных багфиксов.","https:\u002F\u002Fgithub.com\u002Fvmkteam\u002Fclaude-plugins\u002Fblob\u002Fmaster\u002Fplugins\u002Fdeveloper\u002Fskills\u002Fsolve\u002FSKILL.md",[],[97,102,69,103,104,105,106],"id","hitl","fast","preflight","task",{"skillNo":108,"name":109,"nameZh":6,"description":110,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":111,"categories":112,"keywords":113,"publishedAt":31},"01M6MX2H6TF2VW75PCPGWR2SKZ","skills-check","Skills Check — smoke-тесты интеграций из project-index.md. Используй для проверки что pcurl-профили и URL всех data-source скиллов ещё живы.","https:\u002F\u002Fgithub.com\u002Fvmkteam\u002Fclaude-plugins\u002Fblob\u002Fmaster\u002Fplugins\u002Fdeveloper\u002Fskills\u002Fskills-check\u002FSKILL.md",[],[114,115,116,117,118,24,119,120,121,122,123,124,125,126],"skills check","check","URL","index","data","smoke","probe","project","false","data-source","true","source","fork",{"skillNo":128,"name":129,"nameZh":6,"description":130,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":131,"categories":132,"keywords":133,"publishedAt":31},"01M6MX2E5FRMD4NGJGS6CWCP1H","sentry","Sentry — мониторинг ошибок (issues, events, releases) через REST API. Используй когда нужно найти ошибку по ключевым словам, разобрать stack trace, проверить что релиз поднялся или сопоставить ошибку с коммитом.","https:\u002F\u002Fgithub.com\u002Fvmkteam\u002Fclaude-plugins\u002Fblob\u002Fmaster\u002Fplugins\u002Fdeveloper\u002Fskills\u002Fsentry\u002FSKILL.md",[],[129,23,74,75,77,134,135,136,137,24],"events","trace","releases","stack"]