Заметки и сниппеты

Личная коллекция команд и запросов

PostgreSQL / Supabase

Колонки таблицы одной строкой

Список колонок через запятую — для копирования в SELECT, INSERT или быстрой инспекции схемы.

SQL
SELECT string_agg(column_name, ', ' ORDER BY ordinal_position) AS columns
FROM information_schema.columns
WHERE table_schema = 'public'
  AND table_name = 'workflow_costs';

Перед кликом «Copy» скопируйте имя нужной таблицы — оно подставится автоматически.

Полная структура таблицы

Имя, тип, nullable и default для каждой колонки. Используется при дебаге миграций и проверке после ALTER TABLE.

SQL
SELECT column_name, data_type, is_nullable, column_default
FROM information_schema.columns
WHERE table_schema = 'public'
  AND table_name = 'llm_generated_articles'
ORDER BY ordinal_position;

Атомарная архивация статьи

Удаление из основной таблицы с одновременной вставкой в архив. Атомарно — без промежуточных состояний.

SQL
WITH deleted AS (
  DELETE FROM llm_generated_articles
  WHERE article_id = 'ARTICLE_UUID'
    AND batch_id = 'BATCH_ID'
  RETURNING *
)
INSERT INTO llm_generated_articles_archive
SELECT * FROM deleted;

Перед копированием положите в буфер UUID статьи — он подставится в article_id. batch_id отредактируйте после вставки в SQL Editor.

Восстановление статьи из архива

Обратная операция — переносит запись из _archive назад в основную таблицу.

SQL
WITH restored AS (
  DELETE FROM llm_generated_articles_archive
  WHERE article_id = 'ARTICLE_UUID'
    AND batch_id = 'BATCH_ID'
  RETURNING *
)
INSERT INTO llm_generated_articles
SELECT * FROM restored;

Стоимость и длительность последнего запуска

Агрегат по последнему run_id в llm_usage_log: длительность, число вызовов, токены, USD.

SQL
SELECT
  run_id,
  MIN(created_at) AS начало,
  MAX(created_at) AS конец,
  MAX(created_at)::timestamp - MIN(created_at)::timestamp AS длительность,
  COUNT(*)        AS вызовов_llm,
  SUM(input_tokens)        AS input_tok,
  SUM(output_tokens)       AS output_tok,
  ROUND(SUM(estimated_cost_usd)::numeric, 4) AS стоимость_usd
FROM llm_usage_log
WHERE run_id = (
  SELECT run_id FROM llm_usage_log
  ORDER BY created_at DESC
  LIMIT 1
)
GROUP BY run_id;

Полная сумма за сегодня

Сводка по всем запускам с начала суток — завершённым и незавершённым: общий cost, токены, число уникальных run_id и LLM-вызовов.

SQL
SELECT
  ROUND(SUM(estimated_cost_usd)::numeric, 4) AS total_cost_usd,
  SUM(input_tokens)                          AS total_in_tok,
  SUM(output_tokens)                         AS total_out_tok,
  COUNT(DISTINCT run_id)                     AS distinct_runs,
  COUNT(*)                                   AS total_llm_calls
FROM llm_usage_log
WHERE created_at >= CURRENT_DATE;

CURRENT_DATE — полночь по таймзоне Postgres-сессии. Если БД в UTC, «сегодня» отсчитывается с 00:00 UTC.

YTK — YouTube Knowledge Pipeline

Universal Collector (1U) → Universal Scorer (2U) → ytk_channels. Канал = данные, не код.

Сводка по всем каналам

Что собрано и отскорено по каждому каналу: видео, с метриками, с субтитрами, scraped/skipped, уже со score, диапазон дат.

SQL
SELECT
  channel_handle,
  COUNT(*)                                              AS videos,
  COUNT(*) FILTER (WHERE view_count IS NOT NULL)        AS with_metrics,
  COUNT(*) FILTER (WHERE raw_subtitles IS NOT NULL)     AS with_subs,
  COUNT(*) FILTER (WHERE processing_status = 'scraped') AS scraped,
  COUNT(*) FILTER (WHERE processing_status = 'skipped') AS skipped,
  COUNT(*) FILTER (WHERE score IS NOT NULL)             AS scored,
  MIN(published_at)::date                               AS oldest,
  MAX(published_at)::date                               AS newest
FROM youtube_knowledge
GROUP BY channel_handle
ORDER BY videos DESC;

Проверка целостности канала

Битые строки и рассинхрон: видео в статусе scraped, но без субтитров (должно быть 0). Замените handle.

SQL
SELECT
  COUNT(*)                                                 AS total,
  COUNT(*) FILTER (WHERE title IS NULL OR title = '')      AS no_title,
  COUNT(*) FILTER (WHERE view_count IS NULL)               AS no_views,
  COUNT(*) FILTER (WHERE raw_subtitles IS NULL
                    AND processing_status = 'scraped')     AS scraped_but_no_subs
FROM youtube_knowledge
WHERE channel_handle = 'elton_labs';

Лучшие видео (score ≥ 7)

Топовые видео по оценке с датой публикации. Можно убрать фильтр канала для всех сразу.

SQL
SELECT video_id, score, published_at::date AS published, title
FROM youtube_knowledge
WHERE score >= 7
  AND channel_handle = 'elton_labs'
ORDER BY score DESC, published_at DESC;

video_id одной строкой (экспорт)

Все ID лучших видео через запятую — одна ячейка для копирования (например в поле video_ids Collector).

SQL
SELECT string_agg(video_id, ',') AS ids
FROM youtube_knowledge
WHERE score >= 7
  AND channel_handle = 'elton_labs';

Распределение оценок по каналу

Сколько видео на каждый балл и средняя оценка — для калибровки scorer-промпта.

SQL
SELECT score, COUNT(*) AS videos
FROM youtube_knowledge
WHERE channel_handle = 'elton_labs'
  AND score IS NOT NULL
GROUP BY score
ORDER BY score DESC;

Пере-скоринг: сброс оценок

Обнуляет score канала, чтобы Scorer прогнал заново (берёт scraped + score IS NULL). Промпт не меняется.

SQL
UPDATE youtube_knowledge
SET score = NULL,
    score_reason = NULL,
    score_weaknesses = NULL,
    processing_status = 'scraped'
WHERE channel_handle = 'elton_labs';

Пере-сбор: удаление для повторного Collector

Collector дедуплицирует и НЕ обновляет существующие строки. Для пересбора (после фикса субтитров) — удалить пустые.

SQL
DELETE FROM youtube_knowledge
WHERE channel_handle = 'elton_labs'
  AND raw_subtitles IS NULL;

Список каналов в ytk_channels

Конфиг каналов: handle, ниша, длина scorer-промпта, статус. prompt_len > 0 = промпт на месте.

SQL
SELECT channel_handle, channel_id, niche,
       scorer_skill_name,
       length(scorer_system_prompt) AS prompt_len,
       is_active, scrape_status
FROM ytk_channels
ORDER BY channel_handle;

Добавить новый канал

Шаблон INSERT. $prompt$…$prompt$ — dollar-quoting для кириллицы/спецсимволов в промпте. Промпт ниша-специфичный.

SQL
INSERT INTO ytk_channels (
  channel_id, channel_handle, channel_title,
  scorer_skill_name, niche, scorer_system_prompt
) VALUES (
  'UC...',
  'channel_handle',
  'Channel Title',
  'youtube-scorer-NAME-v1',
  'niche-slug',
  $prompt$Ты — асессор видео канала ...
ШКАЛА ОЦЕНОК: ...
Выдай JSON: {"score": 1-10, "weaknesses": "...", "reason": "..."}$prompt$
);

Стоимость последних прогонов Scorer

Сводный cost по запускам скоринга. Должен быть ненулевым (агрегируется из llm_usage_log по run_id).

SQL
SELECT workflow_name, total_tokens, total_cost_usd, status, created_at
FROM workflow_costs
WHERE category = 'youtube-knowledge-scoring'
ORDER BY created_at DESC
LIMIT 10;

PowerShell

Функции Push-Site и Edit-Site в профиль

Запись в $PROFILE, чтобы любой статический сайт из ~/Sites/<name>/ заливался одной командой Push-Site <name>.

PowerShell
function Push-Site {
    param([Parameter(Mandatory)][string]$Name)
    $local = "$HOME\Sites\$Name\index.html"
    if (-not (Test-Path $local)) {
        Write-Host "Файл не найден: $local" -ForegroundColor Red
        return
    }
    scp $local "n8n:/opt/static/$Name/index.html"
    if ($LASTEXITCODE -eq 0) {
        Write-Host "✓ https://$Name.peniaze-ai.work" -ForegroundColor Green
    }
}

function Edit-Site {
    param([Parameter(Mandatory)][string]$Name)
    notepad "$HOME\Sites\$Name\index.html"
}

Цикл правок: Edit-Site notes → правите → сохраняете → Push-Site notesCtrl+F5 в браузере.

Сервер (n8n / Caddy)

Перезагрузка Caddy после правки конфига

Без рестарта контейнера. Выполнять после редактирования /opt/n8n/Caddyfile.

Bash
docker exec n8n-caddy-1 caddy reload --config /etc/caddy/Caddyfile

Поиск в выводе Windows Terminal

Поиск по буферу в Windows Terminal

Ctrl + Shift + F — открывает строку поиска. Вводите UA='Mozilla (или любую уникальную часть), Enter — терминал перематывает к найденному месту и подсвечивает совпадение. Стрелки / — следующее/предыдущее вхождение.

Дальше выделяете мышью с того места, где курсор остановился, до конца нужного фрагмента, и Ctrl + Shift + C — копирует выделенное.

Если используете старый conhost — там Ctrl + F в Mark mode, аналогично.

Запись сессии в файл с поиском по контексту

Для длинных диагностических сессий — пишем всё в файл, потом ищем с захватом N строк после маркера.

PowerShell
# Записать всю сессию в файл
Start-Transcript -Path "$HOME\Downloads\diag-$(Get-Date -Format 'yyyyMMdd-HHmm').log"

# ... ваши ssh-команды ...

Stop-Transcript

# Затем искать в файле:
Select-String -Path "$HOME\Downloads\diag-*.log" -Pattern "UA='Mozilla" -Context 0,200

-Context 0,200 — совпадение + 200 строк после.

Разовый SSH с записью вывода в файл

Команды из bash-скрипта пайпятся через ssh, вывод одновременно идёт на экран и в лог.

PowerShell
ssh n8n 'bash -s' < commands.sh `
  | Tee-Object -FilePath "$HOME\Downloads\out.log"
Ничего не найдено