Назад к обновлениям
New releaseSep 4, 2026

sonar v0.4.1

Инструмент командной строки для проверки и управления службами, прослушивающими порты localhost

Поделиться
██████    ███    ██████    ██    ██████
██           ██      ██  ██  ██      ██
██      ██   ██  ██  ██  ██  ██      ██
  ██  ██   ██  ██  ██  ██  ██  ████
    ██  ██       ██  ██  ██  ██  ██  ██
██████    ███    ██  ██  ██  ██  ██  ██

Знайте, что работает на вашей машине.

Sonar показывает всё, что слушает на localhost, и приводит это в порядок: каждый порт принадлежит группе — обычно это репозиторий, из которого он был запущен, — а внутри этой группы именованному сервису. Запускайте свои dev-серверы с помощью sonar start, и весь проект становится одной сущностью, которую можно вывести в виде дерева, дождаться, отслеживать и остановить одной командой. Docker-контейнеры, проекты Compose и процессы, запущенные вручную, тоже подхватываются, без какой-либо конфигурации.

$ sonar list --tree
my-app  (3 ports, running)                        ~/code/my-app
├─ 5432  db          postgres:17                  http://localhost:5432
├─ 5173  frontend    vite (v5.4)                  http://localhost:5173
└─ 8000  api         uvicorn app:app              http://localhost:8000
ungrouped (1 port)
└─ 3000  next-server (v16.1.6)                    http://localhost:3000

Установка

Homebrew (macOS / Linux)

brew install raskrebs/sonar/sonar

Homebrew 6 отказывается загружать формулы из сторонних tap-репозиториев, пока вы один раз не подтвердите доверие к tap (Error: Refusing to load formula raskrebs/sonar/sonar from untrusted tap):

brew trust raskrebs/sonar

Скрипт установки

curl -sfL https://raw.githubusercontent.com/raskrebs/sonar/main/scripts/install.sh | bash

Загружает последний бинарный файл в ~/.local/bin и добавляет его в ваш PATH при необходимости. Перезапустите терминал или выполните source ~/.zshrc.

В Windows (PowerShell):

irm https://raw.githubusercontent.com/raskrebs/sonar/main/scripts/install.ps1 | iex

Пользовательское расположение установки:

curl -sfL https://raw.githubusercontent.com/raskrebs/sonar/main/scripts/install.sh | SONAR_INSTALL_DIR=/usr/local/bin bash

Установите конкретную версию:

curl -sfL https://raw.githubusercontent.com/raskrebs/sonar/main/scripts/install.sh | SONAR_VERSION=vX.Y.Z bash
$env:SONAR_VERSION="vX.Y.Z"; irm https://raw.githubusercontent.com/raskrebs/sonar/main/scripts/install.ps1 | iex

Использование Go

go install github.com/raskrebs/sonar@latest

Завершения оболочки (автодополнение номеров портов по Tab):

sonar completion zsh > "${fpath[1]}/_sonar"   # zsh
sonar completion bash > /etc/bash_completion.d/sonar  # bash
sonar completion fish | source                 # fish

Шестьдесят секунд

Опишите, как запускается ваш проект, в файле sonar.yaml в корне репозитория, вместо dev.sh:

name: my-app
services:
  - name: db
    cmd: docker compose up db
    port: 5432
  - name: api
    cmd: uv run uvicorn app:app --port ${port}
    port: auto
    depends_on: [db]
  - name: frontend
    cmd: npm run dev -- --port ${port} --strictPort
    port: auto
    depends_on: [api]
    env:
      VITE_API_URL: ${api.url}

Затем запустите его:

sonar start
  ✓ db        http://localhost:5432   pid 41022  ~/.config/sonar/logs/my-app/db.log
  ✓ api       http://localhost:21408  pid 41040  ~/.config/sonar/logs/my-app/api.log
  ✓ frontend  http://localhost:21409  pid 41077  ~/.config/sonar/logs/my-app/frontend.log

3 started
following the logs; Ctrl+C stops the 3 services started here
api      | INFO:     Uvicorn running on http://127.0.0.1:21408
frontend | VITE v5.4  ready in 312 ms

Sonar выбирает свободный порт для каждого сервиса с port: auto и сообщает каждому сервису, где находятся остальные, поэтому второе рабочее дерево запускается рядом с первым без единого конфликта портов. sonar start -d делает то же самое в фоновом режиме, а sonar down останавливает его и освобождает порты. В другом терминале:

sonar list --tree
my-app  (3 ports, running)                        ~/code/my-app
├─ 5432   db          postgres:17                 http://localhost:5432
├─ 21408  api         uvicorn app:app             http://localhost:21408
└─ 21409  frontend    vite (v5.4)                 http://localhost:21409

Нет sonar.yaml? sonar start -- npm run dev запускает одну команду как именованный сервис, а sonar list и sonar kill работают со всем, что слушает.

Примеры ниже, помеченные # check, выполняются на свежей сборке скриптом scripts/readme-check.sh при каждом запуске CI.

Команды

sonar list

sonar list
sonar list --tree
sonar list --group my-app
sonar list --json
# check
sonar list --stats             # CPU, memory, threads, uptime, state
sonar list --health            # HTTP health checks
sonar list --filter docker     # only Docker ports
sonar list --sort name         # port | pid | name | type
sonar list -a                  # include desktop apps
sonar list -c port,process,group,cpu,mem
sonar list --host user@server  # scan a remote machine over SSH

Столбцы по умолчанию: port, process, group, container, image, containerport, url, где process показывает имя, которое вы дали порту (sonar rename), затем имя сервиса, затем то, что было обнаружено.

Доступные столбцы: port, process, pid, type, url, group, cpu, mem, threads, uptime, state, connections, health, latency, container, image, containerport, compose, project, user, bind, ip.

Десктопные приложения и системные службы, которые случайно прослушивают порты — Figma, Discord, Spotify, ControlCenter, macOS .app bundles, демоны /System/Library/ — скрыты, если не передать -a.

sonar start

Запустить проект из его sonar.yaml:

sonar start                    # every service in the nearest sonar.yaml
sonar start api frontend       # only these (they still wait for their dependencies)
sonar start ../other-project   # the sonar.yaml in or above another directory
sonar start -d                 # in the background, like `sonar up`

На переднем плане sonar запускает сервисы через демон, следит за их логами, добавляя имя сервиса перед каждой строкой, и при нажатии Ctrl+C останавливает запущенные им сервисы. Сервис, который уже был запущен, не затрагивается, и sonar завершается сам, как только все запущенные им сервисы завершатся.

Аргументы перед -- задают каталог или сервисы; всё после -- — это команда. Либо запустите одну команду как именованный сервис в группе:

sonar start -- npm run dev
sonar start --group my-app --name frontend -- npm run dev
sonar start --port 5173 -- npm run dev        # expected port, before it binds
sonar start --detach --name api -- uv run uvicorn app:app
sonar start --list

Ничего передавать не нужно:

  • Group — --group, иначе name из ближайшего sonar.yaml, иначе имя каталога git-корня (worktree становится repo@worktree), иначе имя текущего каталога.
  • Name — --name, иначе сервис из sonar.yaml, чей cmd совпадает, иначе выводится из команды (npm run dev → dev, uv run api → api, python -m uvicorn → uvicorn, ./dev.sh → dev.sh).
  • Port — --port — это подсказка, а не привязка: запуск отображается как starting, пока порт фактически не начнёт прослушиваться, и демон использует его для сопоставления процесса с портом.

Дочерний процесс наследует stdin, stdout, stderr, cwd и окружение, а также SONAR_GROUP, SONAR_NAME и SONAR_RUN_ID. Он получает собственную группу процессов, поэтому sonar kill уничтожает всё дерево — dev-сервер с его наблюдателями и воркерами. Ctrl+C перенаправляется, и sonar завершается с кодом выхода дочернего процесса.

Категории