Публічний реєстр
Звіт про здоров'я програмного забезпеченнясхема 0.27.0 · метрики 2.5.0 · 2026-08-01 00:34 UTC

lethevimlet / sshift

A modern, responsive web-based SSH and SFTP terminal client for the AI stack

JavaScript · HTMLMIT★ 35 зірок⑂ 3 форкиз квіт. 2026 р.Переглянути на GitHub ↗
ТипБібліотекаМережевий сервісяк це визначено

lethevimlet/sshift має індекс здоров’я 56 зі 100, що відповідає смузі «Помірний». Найвищий показник — Engineering Quality (91/100), найнижчий — Security (35/100). Останнє оновлення було 9 днів тому. Більшість нещодавньої роботи виконує один учасник.

56
загалом / 100
Помірний

Індекс здоров'я програмного забезпечення

Метрики згруповано у зважені категорії на шкалі 1–100. Загальна оцінка починається як їхнє зважене середнє, відкаліброване за розподілом публічного реєстру, тож діапазони мають перцентильний зміст; коли публічні дані активують Політику юрисдикцій високого ризику, рейтинг коригується й отримує верхню межу 34 («У зоні ризику»).

56
Винятковий93-100Верхній щабель реєстру (≈ топ-5%); відповідає практично всім перевіреним критеріям
Відмінний80-92Сильний за всіма напрямами; незначні прогалини
Добрий65-79Здоровий; прогалини обмежені та керовані
Помірний50-64Прийнятний, але з помітними прогалинами; рекомендовано перевірку
Слабкий35-49Суттєві недоліки в кількох сферах
У зоні ризику20-34Суттєві слабкі місця; впровадження потребує обережності
Критичний1-19Серйозні проблеми (покинутий, єдиний мейнтейнер, без базової гігієни)
ЖиттєздатністьСпільнота тавпровадженняСталість таврядуванняІнженернаякістьБезпекаГотовність доШІ

Профіль оцінок

Кожна вісь — окрема категорія. Форма важить більше, ніж середнє: здоровий об'єкт заповнює всю фігуру, тоді як профіль із піками та провалами означає, що сила в одному вимірі маскує ризик в іншому.

Зважений загальний бал 54 калібровано до 56 за шкалою опублікованого індексу (калібрування реєстру 2026-08-02).

Власність

LetheОсобистий обліковий запис
10 підписників12 публічних репозиторіївз груд. 2012 р.@vimlet

Цей репозиторій належить особистому обліковому запису. Проєкт з єдиним власником несе більший ризик безперервності, ніж підтримуваний організацією.

Пакетні екосистеми

РеєстрПакетВерсіяЗавантажень / місВерсіїОстання публікаціяТеги
npm@lethevimlet/sshift1.6.12 001779 днів томуsshsftpterminalwebxtermweb-terminalssh-clientsftp-clientbrowser-terminal

Метрики за категоріями

Життєздатність

Чи живий проєкт — чи пишеться код і чи виходять релізи?

39Слабкий · 21% загального індексу
Як обчислюється оцінка
28.8/36Свіжість push — останній push 9 дн. тому
8.3/36Ритм комітів — 12/52 тижнів із комітами
18/18Обсяг комітів — 244 комітів за останній рік
10/10OpenSSF Scorecard: Maintained — 30 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 10
Використані вхідні дані
commits_last_year244
human_commit_share1
days_since_last_push9
active_weeks_last_year12
Як обчислюється оцінка
0/27Випускає релізи — релізів не опубліковано
0/36Свіжість релізів — релізів немає
0/27Ритм релізів — релізів немає
0/10OpenSSF Scorecard: Signed-Releases — немає даних
Використані вхідні дані
releases_count0
Виключено з оцінювання (немає даних або не застосовно): OpenSSF Scorecard: Signed-Releases. Залишкові ваги перенормовано.

Спільнота та впровадження

Чи має проєкт користувачів, завантаження, увагу та влаштовані умови для контриб’юторів?

49Слабкий · 17% загального індексу
Як обчислюється оцінка
24.8/60Зірки — 35 зірок
2.5/25Форки — 3 форків
0/15Спостерігачі — 0 спостерігачів
Використані вхідні дані
forks3
stars35
watchers0
growth_stateunverified
growth_factor_pct100
growth_unverified_reasonno_history
Як обчислюється оцінка
22.5/22.5README
22.5/22.5Ліцензія — визнана ліцензія (MIT)
18/18Настанови CONTRIBUTING
0/13.5Кодекс поведінки
0/7.2Шаблон issue
0/6.3Шаблон PR
Використані вхідні дані
has_readmeтак
has_licenseтак
readme_badges
has_contributingтак
has_issue_templateні
has_code_of_conductні
readme_badge_services
has_pull_request_templateні
Як обчислюється оцінка
44/80Щомісячні завантаження — 2 001 завантажень/місяць у npm
0/20Залежні пакети в реєстрі — ця екосистема цього не повідомляє
Використані вхідні дані
packages@lethevimlet/sshift
dependents
ecosystemsnpm
total_downloads
monthly_downloads2 001
Виключено з оцінювання (немає даних або не застосовно): Залежні пакети в реєстрі. Залишкові ваги перенормовано.

Сталість та врядування

Чи переживе проєкт своїх людей — бас-фактор, реактивність, хто за ним стоїть і як супроводжуються пакети?

52Помірний · 23% загального індексу
Як обчислюється оцінка
9/54Бас-фактор — на 1 контриб’ютор(ів) припадає половина всіх комітів
0/22.5Розподіл комітів — головний контриб’ютор — автор 100% комітів
1.4/13.5Широта контриб’юторів — 1 контриб’юторів
3/10OpenSSF Scorecard: Contributors — project has 1 contributing companies or organizations -- score normalized to 3
Використані вхідні дані
bus_factor1
contributors_sampled1
top_contributor_share1
Як обчислюється оцінка
0/42Вирішення issue — немає issue або даних
30/30Прийняття PR — злито 1/1 вирішених PR
0/13Newcomer PR acceptance — за 30 дн. не вирішено жодного PR від новачка
0/15OpenSSF Scorecard: Code-Review — Found 0/30 approved changesets -- score normalized to 0
Використані вхідні дані
merged_prs1
open_issues0
closed_issues0
prs_merged_7d
prs_decided_7d
prs_merged_30d
prs_decided_30d
issue_closed_ratio
closed_unmerged_prs0
first_time_authors_30d
first_time_prs_merged_30d
first_time_prs_decided_30d
Виключено з оцінювання (немає даних або не застосовно): Вирішення issue, newcomer_pr_acceptance. Залишкові ваги перенормовано.
Як обчислюється оцінка
10/30Підтримка власника — особистий (користувацький) обліковий запис
0/20Верифікований домен — не застосовно до користувацьких облікових записів
7.5/25Охоплення власника — 10 підписників у lethevimlet
20.1/25Послужний список — 12 публічних репозиторіїв, вік облікового запису ~13 р.
Використані вхідні дані
followers10
owner_typeUser
is_verified
owner_loginlethevimlet
public_repos12
account_age_days4 987
Виключено з оцінювання (немає даних або не застосовно): Верифікований домен. Залишкові ваги перенормовано.

Супровід пакетів

100Винятковий
Як обчислюється оцінка
25/25Опубліковано й доступно — 1 пакет(ів) у npm
35/35Свіжість публікацій — остання публікація 9 дн. тому
20/20Історія версій — 77 опублікованих версій
20/20Не застарілий — активний, не deprecated і не yanked
Використані вхідні дані
packages@lethevimlet/sshift
ecosystemsnpm
any_deprecatedні
min_days_since_publish9

Інженерна якість

Чи наявні базові інженерні практики та документація?

91Відмінний · 19% загального індексу
Як обчислюється оцінка
24/24Процеси CI — 3 процес(ів) CI
24/24Наявні тести
16/16Конфігурація лінтера
9.6/9.6Pre-commit-хуки
0/6.4.editorconfig
0/20OpenSSF Scorecard: CI-Tests — немає даних
Використані вхідні дані
has_ciтак
has_testsтак
has_editorconfigні
has_linter_configтак
has_precommit_configтак
Виключено з оцінювання (немає даних або не застосовно): OpenSSF Scorecard: CI-Tests. Залишкові ваги перенормовано.

Документація

90Відмінний
Як обчислюється оцінка
30/30README
25/25Каталог документації
15/15Сайт документації / домашня сторінка — https://getsshift.dev
10/10Опис репозиторію
0/10Теми
10/10Wiki
Використані вхідні дані
topics
has_wikiтак
homepagehttps://getsshift.dev
has_readmeтак
has_docs_dirтак
has_descriptionтак

Безпека

Чи міцні видимі практики безпеки й ланцюга постачання, без непослабленої пов’язаності з юрисдикціями високого ризику?

35Слабкий · 16% загального індексу

Стан безпеки

35Слабкий
Як обчислюється оцінка
7.5/7.5Binary-Artifacts — no binaries found in the repo
0/7.5Branch-Protection — branch protection not enabled on development/release branches
0/2.5CI-Tests — немає даних
0/2.5CII-Best-Practices — no effort to earn an OpenSSF best practices badge detected
0/7.5Code-Review — Found 0/30 approved changesets -- score normalized to 0
0.8/2.5Contributors — project has 1 contributing companies or organizations -- score normalized to 3
10/10Dangerous-Workflow — no dangerous workflow patterns detected
0/7.5Dependency-Update-Tool — no update tool detected
0/5Fuzzing — project is not fuzzed
2.5/2.5Ліцензія — license file detected
7.5/7.5Maintained — 30 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 10
5/5Packaging — packaging workflow detected
0/5Pinned-Dependencies — dependency not pinned by hash detected -- score normalized to 0
0/5SAST — no SAST tool detected
0/5Security-Policy — security policy file not detected
0/7.5Signed-Releases — немає даних
0/7.5Token-Permissions — detected GitHub workflow tokens with excessive permissions
0/7.5Vulnerabilities — 25 existing vulnerabilities detected
Використані вхідні дані
sourceopenssf_scorecard
checks_evaluated16
scorecard_versionv5.5.0
checks_inconclusive2
scorecard_aggregate3,5
Виключено з оцінювання (немає даних або не застосовно): ci_tests, signed_releases. Залишкові ваги перенормовано.
Як обчислюється оцінка
12.1/35Прямі залежності без відомих сповіщень — уражено 2: uuid 9.0.0 (high 7.5), express 4.18.2 (moderate 6.1)
5/25Непрямі залежності без відомих сповіщень — уражено 9: body-parser 1.20.1 (high 7.5), engine.io 6.5.5 (high 7.5), path-to-regexp 0.1.7 (high 7.5), ще +6
18.6/40Немає задавнених сповіщень — 9 пакет(ів) зі сповіщеннями без реакції понад 90 дн.; найдавніше опубліковано 858 дн. тому
Використані вхідні дані
sourceosv
advisories17
affected_packages11
assessed_packages132
unassessed_packages0
affected_by_severityhigh 5, moderate 4, low 2
direct_affected_packages2
Звірено з runtime-замиканням залежностей npm:@lethevimlet/sshift@1.6.1 — тим, що тягне за собою встановлення опублікованого пакета, — 132 пакетів. Досяжність не аналізується.

Готовність до ШІ

Наскільки репозиторій оснащений для розробки та супроводу за участі ШІ-агентів? Має свідомо малу вагу (4%): агентний інструментарій — реальний сигнал супроводу, але репозиторій без нього все одно може отримати 100/100.

54Помірний · 4% загального індексу
Як обчислюється оцінка
45/45Інструкції для агентів — AGENTS.md
0/15Машиночитана документація (llms.txt)
27.2/40Читабельна історія комітів — намір зазначено у 51 з 100 людських комітів (структурований заголовок або пояснювальний текст)
Використані вхідні дані
has_llms_txtні
legible_history_share0,51
agent_instruction_filesAGENTS.md
agent_instruction_max_bytes9 064
Як обчислюється оцінка
0/18Розгортання однією командою
22/22Автоматизовані тести
11/11Конфігурація лінтера / форматера
0/11Статична перевірка типів
10/10Відтворюване середовище — Dockerfile, lockfile
0/10Підтверджена практика роботи з агентами — серед останніх 100 комітів немає створених агентом
0/8Автоматизоване супроводження — автоматичних оновлень залежностей не виявлено
0/10OpenSSF Scorecard: Pinned-Dependencies — dependency not pinned by hash detected -- score normalized to 0
Використані вхідні дані
has_nixні
has_testsтак
lockfilespackage-lock.json, pnpm-lock.yaml
has_dockerfileтак
typed_languageні
bootstrap_files
has_devcontainerні
has_linter_configтак
typecheck_configs
agent_commit_share0
toolchain_manifests
dependency_bot_commit_share0
Як обчислюється оцінка
0/45Типізований код — JavaScript без конфігурації перевірки типів
50/55Керовані розміри файлів — 7/77 файлів вихідного коду понад 60 КБ
Використані вхідні дані
primary_languageJavaScript
largest_source_bytes497 398
source_files_sampled77
oversized_source_files7

Ключові факти

35зірок GitHub
1контриб'юторів
244комітів за останні 12 місяців
9днів від останнього пушу
0релізів
1бас-фактор
0відкритих issue
npmпакетних екосистем

Попередження щодо збору даних

  • Star history unavailable: GitHub GraphQL error: Resource not accessible by personal access token
  • GitHub dependency-graph SBOM unavailable (404); the dependency graph may be disabled for this repository

Докладніше

Історія зірок і форків 0 ★ / 3 ⇿
0Зірки
3Форки

Коли додано кожну зірку й форк — зібрано з GitHub і згруповано за днями. Кумулятивне зростання розміщено просто над денними додаваннями, з яких воно складається, тож їх видно одне проти одного: рівномірне органічне накопичення виглядає зовсім інакше, ніж різкий короткочасний сплеск. Там, де цю різницю можна виміряти, її подано як автентичність росту.

12233312026-042026-042026-05
OpenSSF Scorecard 3.5 / 10
3.5сукупно

Незалежна, не прив'язана до інструментів оцінка безпеки від відкритого проєкту OpenSSF Scorecard. Кожна перевірка винагороджує практику безпеки, а не інструмент конкретного постачальника. Перевірки, які Scorecard не зміг визначити, позначено н/д і виключено з оцінки безпеки (вони ніколи не зараховуються як нуль).Scorecard v5.5.0 · 2026-08-01 00:33 UTC

10Binary-Artifactsno binaries found in the repo
0Branch-Protectionbranch protection not enabled on development/release branches
н/дCI-Testsno pull request found
0CII-Best-Practicesno effort to earn an OpenSSF best practices badge detected
0Code-ReviewFound 0/30 approved changesets -- score normalized to 0
3Contributorsproject has 1 contributing companies or organizations -- score normalized to 3
10Dangerous-Workflowno dangerous workflow patterns detected
0Dependency-Update-Toolno update tool detected
0Fuzzingproject is not fuzzed
10Licenselicense file detected
10Maintained30 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 10
10Packagingpackaging workflow detected
0Pinned-Dependenciesdependency not pinned by hash detected -- score normalized to 0
0SASTno SAST tool detected
0Security-Policysecurity policy file not detected
н/дSigned-Releasesno releases found
0Token-Permissionsdetected GitHub workflow tokens with excessive permissions
0Vulnerabilities25 existing vulnerabilities detected
Прямі залежності 17
РеєстрПакетОбмеження версіїМаніфест
npm@xterm/addon-fit0.11.0package.json
npm@xterm/addon-image0.9.0package.json
npm@xterm/addon-search0.16.0package.json
npm@xterm/addon-serialize0.14.0package.json
npm@xterm/addon-unicode110.9.0package.json
npm@xterm/addon-web-links0.12.0package.json
npm@xterm/addon-webgl0.19.0package.json
npm@xterm/headless6.0.0package.json
npm@xterm/xterm6.0.0package.json
npmdotenv17.4.0package.json
npmexpress4.18.2package.json
npmhttpolyglot0.1.2package.json
npmlucide1.7.0package.json
npmselfsigned5.5.0package.json
npmsocket.io4.7.2package.json
npmssh21.15.0package.json
npmuuid9.0.0package.json
Усі залежності не зібрано

Не вдалося зібрати розв'язаний набір залежностей для цього звіту: GitHub dependency-graph SBOM unavailable (404); the dependency graph may be disabled for this repository

Сповіщення про залежності 11

Встановлення npm:@lethevimlet/sshift@1.6.1 тягне 132 пакетів, прямих і транзитивних: 11 мають відомі сповіщення, з них 2 — прямі залежності.

ПакетВерсіяЗв'язокКритичністьСповіщеньВиправлено в
uuid9.0.0прямависока113.0.1
body-parser1.20.1непрямависока22.3.0
engine.io6.5.5непрямависока16.6.7
path-to-regexp0.1.7непрямависока38.0.0
ws8.17.1непрямависока28.21.0
express4.18.2прямапомірна25.0.0-beta.3
qs6.11.0непрямапомірна26.14.2
send0.18.0непрямапомірна10.19.0
serve-static1.15.0непрямапомірна12.1.0
cookie0.4.2непряманизька10.7.0
cookie0.5.0непряманизька10.7.0

Сповіщення означає, що версія, записана в графі залежностей, потрапляє в уражений діапазон. Досяжність не аналізується, а граф містить піниї розробки й тестування — знахідка може стосуватися інструментів, а не поставленого коду.

Звіт у форматі JSON машиночитний
{
  "data": {
    "repo": {
      "topics": [],
      "is_fork": false,
      "size_kb": 7453,
      "has_wiki": true,
      "homepage": "https://getsshift.dev",
      "languages": {
        "CSS": 115037,
        "HTML": 614396,
        "Shell": 33274,
        "Dockerfile": 3633,
        "JavaScript": 1011287,
        "PowerShell": 39771
      },
      "pushed_at": "2026-07-22T16:15:20Z",
      "created_at": "2026-04-02T02:10:23Z",
      "owner_type": "User",
      "updated_at": "2026-07-30T02:19:45Z",
      "description": "A modern, responsive web-based SSH and SFTP terminal client for the AI stack",
      "is_archived": false,
      "is_disabled": false,
      "license_spdx": "MIT",
      "default_branch": "main",
      "license_spdx_raw": "MIT",
      "primary_language": "JavaScript",
      "significant_languages": [
        "JavaScript",
        "HTML"
      ]
    },
    "owner": {
      "blog": "https://vimlet.com",
      "name": "Lethe",
      "type": "User",
      "login": "lethevimlet",
      "company": "@vimlet",
      "location": "Madrid, Spain",
      "followers": 10,
      "avatar_url": "https://avatars.githubusercontent.com/u/2963923?v=4",
      "created_at": "2012-12-04T17:42:19Z",
      "is_verified": null,
      "public_repos": 12,
      "account_age_days": 4987
    },
    "license": {
      "state": "standard",
      "spdx_id": "MIT",
      "raw_spdx": "MIT",
      "file_present": true,
      "scorecard_found": true,
      "profile_has_license": true
    },
    "activity": {
      "releases": [],
      "recent_commits": [
        {
          "oid": "d551d43a298c091d10e0e373670271f3ba075fa8",
          "body": "forceResizeLikeRefit replicates a real window resize for one session\n(synchronous reflow -> measure -> fit -> clearTextureAtlas -> refresh ->\nssh-resize) and is wired into switchTab's safety net, ssh-control-acquired,\nssh-control-taken, and the force-resize button. _syncCharSizeThenClearAtlas\ncommit\n[…]\n in switchTab/setSessionFontSize/\nsetTerminalFontSize + SFTP setters. Adds deterministic regression tests\n(interlace-regression, force-resize-fix) and a manual repro harness.\n\nCo-authored-by: opencode",
          "is_bot": false,
          "headline": "v1.6.1: fix interlace black-band artefact on take-control/tab-grab",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-07-22T16:15:15Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "177b7c78a466cf53d7e80055fcc6ddc48583ebae",
          "body": "…settings\n\n- Add mic button next to keyboard button in tab actions (SSH tabs only;\n  disabled on SFTP). Merged Record/Pause toggle cycles idle -> recording ->\n  stop-and-transcribe, appending each segment to the transcript.\n- Speech-to-Text modal: editable transcript, Record/Pause toggle, Wand butto\n[…]\npe) so they behave identically on mobile/desktop.\n- _wireActionBtn fires touchend on all touch devices (not just isMobile)\n  fixing the 'mic dialog not working on mobile' bug.\n- Bump version to 1.6.0.",
          "is_bot": false,
          "headline": "v1.6.0: speech-to-text with mic button, AI wand cleanup, Speech & AI …",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-07-21T10:19:48Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "43a85892acc72e8e99bebe50ad59f4511452a794",
          "body": "…ew ruler white line\n\n- Fix interlaced black bands when dragging tabs between layout panels\n  (split, 2-columns, 2x2). After moveTabToPanel moves the terminal\n  wrapper DOM to a new container, the WebGL glyph atlas still holds\n  glyphs rasterised at the old panel's cell size. Fitting immediately\n  m\n[…]\nScrollbar: 4px on mobile, 8px on desktop. Slider bg overridden to\n  #3d444d via setProperty('background-color', ..., 'important').\n  MutationObserver enforces dimensions + bg on every xterm re-render.",
          "is_bot": false,
          "headline": "v1.5.2: fix interlace on tab drag between layout panels + hide overvi…",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-07-20T21:59:43Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "b5d2277eeffd6e22b74ec84795c806b18774104d",
          "body": "…ew ruler white line\n\n- Mobile TUI scroll: touch scroll in mouse-tracking mode now sends SGR\n  mouse wheel escape sequences directly via ssh-data instead of\n  dispatching synthetic WheelEvents (which xterm's document-level touch\n  handler interfered with). Checks coreMouseService.activeProtocol\n  in\n[…]\nrent canvas created a visible white strip\n  when terminal had no scrollback or on blur.\n- MutationObserver runs on all platforms to enforce scrollbar\n  dimensions + slider bg on every xterm re-render.",
          "is_bot": false,
          "headline": "fix: mobile TUI scroll, rate-limit toast, thin scrollbar, hide overvi…",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-07-20T21:55:19Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "22c84465965332accf238e50eb035ba20982013a",
          "body": "…use/scroll inactivity lockup\n\nBug 1 — Interlaced lines after Take Control on a refreshed browser tab\n- Root cause: the ssh-screen-sync completion callback wrote the\n  serialized terminal state + called terminal.resize(cols, rows) but\n  did NOT clear the WebGL glyph atlas. The atlas still held glyph\n[…]\n available, and that the\n  sftp-download code shape  does not throw.\n\nTest totals: 94 unit tests (was) -> 101 unit tests (+7 bugfix\nregressions). 0 failing. All touched source files pass node --check.",
          "is_bot": false,
          "headline": "v1.5.1: fix interlace-after-take-control, SFTP download crash, TUI mo…",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-07-20T20:05:37Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "5dba19252ec4f055a0434557b55927bd0d97e742",
          "body": "…down fixes\n\nMarks the consolidated release of v1.4.9 + v1.4.10 changes:\n- Mobile tab-count duplicate-block bug fix + 8-phase hardening\n- xterm fidelity (surrogate split, OSC 52 cross-chunk, sync-window recovery, WebGL re-sync, payload validation, rate limits)\n- Sticky teardown respects remaining vi\n[…]\n tests + integration/browser suites (was 21 baseline)\n- Bug A: ssh-request-sync rate-limit no longer closes tabs (advisory flag)\n- Bug B: server restart = fresh slate, no localStorage auto-reconstruct",
          "is_bot": false,
          "headline": "v1.5.0: bump minor version — robustness pass + mobile tab/sticky tear…",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-07-19T23:32:56Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "900ef22acb3c67d5d3302d997dd04a089c3db355",
          "body": "…art stale-tab leak\n\nBug A — ssh-request-sync rate-limit cascaded into closeTab\n- Root cause: the Phase-3 ssh-request-sync rate limit (added in v1.4.9)\n  emitted an 'ssh-error' with a sessionId. The client's ssh-error\n  handler at app.js:4521 had a catchall closeTab(sessionId) for ANY\n  error messag\n[…]\ns normally (server-restart case isolated)\n\nTest totals: 87 unit tests (was) → 94 unit tests (+7 bugfix regressions) + 11 skipped (SSH-credential-gated). Syntax checked: app.js, ssh.js, ssh-manager.js.",
          "is_bot": false,
          "headline": "v1.4.10: fix mobile dropdown switchTab closing all tabs + server-rest…",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-07-19T23:31:33Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "196bbc20f06448c82b2c09665cf51f0fdc2d2c53",
          "body": "…lity + sticky teardown, add 145 tests + Docker SSH fixture\n\nPhase 1 — Mobile tab-count bug\n- handleTabOpened: remove duplicate createSSHTab/createSFTPTab block\n- server: socket.broadcast.emit('tab-opened') so originator doesn't echo to itself\n- createSSHTab/createSFTPTab: idempotency guards by sess\n[…]\nructure, env vars, fixture docs, troubleshooting\n\nTest totals: 21 passing / 5 skipped (baseline) → 146 passing / 18 skipped / 0 failing in Docker-less env; with Docker fixture: 164 passing / 0 skipped",
          "is_bot": false,
          "headline": "v1.4.9: robustness pass — fix mobile tab-count bug, harden xterm fide…",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-07-19T23:07:00Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "ca041efeca981340b3ce74d377ae3ff2fad411d2",
          "body": "…ce re-measure after font swap\n\nThe v1.4.7 attempt (removing the redundant lineHeight/letterSpacing resets)\ndid not actually fix the interlace bug — those setters were no-ops since\nthe options were already at their base values, so removing them changed\nnothing.\n\nRoot cause: xterm.js v6's CharSizeSer\n[…]\nreshCharAtlas, so by the time fit() runs it sees the real\nfont's cell height and dims cols/rows correctly.  Uses the same private\n_api pattern already in use elsewhere (terminal._core._renderService).",
          "is_bot": false,
          "headline": "v1.4.8: fix interlace (alternating black bands) — force CharSizeServi…",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-07-19T20:31:44Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "e2e330917d99ebb4313f588fb4b841444abbbe21",
          "body": "…nt bugs\n\nRoot cause for both symptoms was the sizing/fit path in _fitTerminal\nand initTerminal.\n\nBug 1 (alternating black horizontal bands between text rows):\n  _fitTerminal reset term.options.lineHeight and letterSpacing to base\n  values immediately before fitAddon.fit().  Those options are alread\n[…]\nbove transient collapsed\n  states), bumped retries to 5, and added two delayed safety-net refits\n  (250ms / 600ms) after the post-fonts-ready fit mirroring the existing\n  retry pattern in switchTab().",
          "is_bot": false,
          "headline": "v1.4.7: fix terminal interlace (black bands) and tiny-terminal-on-mou…",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-07-15T11:26:41Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "2c40067b7c7b3bcda192836b857caa6a41d6bdb0",
          "body": "…tomGlyphs+WebGL as the correct approach",
          "is_bot": false,
          "headline": "v1.4.6: revert broken letterSpacing/lineHeight seam fix; document cus…",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-06-11T15:34:47Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "fc8cd5b64e19abab901802686f60222b68912a5e",
          "body": "Only applies letterSpacing padding when it's imperceptible (≤0.15 CSS px);\nlarger padding that would create visible gaps between characters is skipped\nand we accept the occasional 1px seam instead. This fixes seams on most\nDPRs (e.g. 2x displays typically need ~0.1px padding) while avoiding the\nvisible spacing regression seen in v1.4.2-v1.4.3.",
          "is_bot": false,
          "headline": "v1.4.5: re-enable letterSpacing snapping with tight 0.15px threshold",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-06-11T13:40:08Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "40c353c7dbd0652a4f0f5fbb1bc0f8c7c640e619",
          "body": "The letterSpacing-based subpixel snapping (v1.4.2-v1.4.3) made seams\nmore visible rather than less — it added gaps between all characters\nbecause xterm.js customGlyphs does not fill the letterSpacing portion\nof block characters. Disabled _snapCellWidth and set letterSpacing: 0\nexplicitly. The primary seam mitigation is now customGlyphs: true,\nwhich draws block/box-drawing characters as vector fills that tile the\nfull cell width.",
          "is_bot": false,
          "headline": "v1.4.4: revert letterSpacing snapping, keep customGlyphs for seams",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-06-11T13:22:27Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "616efb13a77838555bca3cf9fa43396b27420391",
          "body": "…ment\n\nThe previous v1.4.2 _snapCellWidth() set letterSpacing AFTER fit(), which\nchanged cell width without recalculating column count — widening every\ncell while xterm still laid out the old narrower columns, making seams\nworse. Now the sequence is: fit → measure → snap letterSpacing →\nrefit if spacing changed, so columns are recalculated with the snapped\n(integer device-pixel) cell width. Two fits is intentional: first\nmeasures, second uses the corrected spacing.",
          "is_bot": false,
          "headline": "v1.4.3: fix subpixel seam snapping — refit after letterSpacing adjust…",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-06-11T13:08:57Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "32cac3eb31af0e1e71d468848c80165e6a480bed",
          "body": "- Add 150ms delayed refit after every tab switch as safety net for\n  layout transitions that haven't settled by the 2nd animation frame\n- Lower _fitTerminal dimension guard from 10px to 2px threshold\n- Set customGlyphs: true and letterSpacing: 0 in terminal options\n- Add _snapCellWidth() to nudge le\n[…]\nional-DPR displays\n- Expand DPR change listener to also refit, re-snap, and rebuild\n  atlas (not just clear it) on zoom/monitor move\n- Call _snapCellWidth after every _fitTerminal and after font-ready",
          "is_bot": false,
          "headline": "v1.4.2: fix small terminal on tab switch, eliminate subpixel seams",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-06-11T12:55:47Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "e1ea1b6adb9124a167bf130be8bcdd5706f6e071",
          "body": "- Lower _fitTerminal dimension guard threshold from 10px to 2px to\n  avoid rejecting containers that are mid-layout-transition\n- Add retry loop in switchTab when _fitTerminal fails (up to 4\n  retries with 50-300ms delays) so tab switching always converges\n- Only emit ssh-resize after successful fit in take-control and\n  become-controller handlers; skip send when fit returns false to\n  avoid sending stale dimensions to the remote PTY\n- Same fix in ssh-connected initial resize",
          "is_bot": false,
          "headline": "v1.4.1: fix terminal small square on tab switch, fix take-control resize",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-06-11T12:38:29Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "f65d9083075a806615e507de054c4dd9215ddbf4",
          "body": "- Resolve user's home directory on SFTP connect via realpath('.')\n- Auto-fallback from '/' to home dir when listing root fails with\n  permission denied (common with chrooted SFTP servers)\n- Send homeDir in sftp-error responses so the client can navigate home\n- Client auto-redirects to home directory\n[…]\n(Bug 3: dim/black panes)\n- Clear WebGL atlas on tab switch, visibility change, and font swap\n- Use requestAnimationFrame for post-layout refits\n- Font-aware initial PTY resize in ssh-connected handler",
          "is_bot": false,
          "headline": "v1.4.0: fix SFTP permission denied on root directories",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-06-11T11:59:37Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "540856225e6a3d3949dcb1bd2dbb40aa13c99be2",
          "body": "- Bug 1 (narrow column wrapping): await font loading before initial\n  fit() and PTY resize so glyph width is measured from the actual\n  monospace font, not the fallback; guard _fitTerminal() against\n  zero/near-zero container dimensions\n- Bug 2 (bottom-row garbage): call terminal.refresh() after eve\n[…]\nbGL on\n  tab activation; always clear atlas when a tab becomes visible\n- Also fix interspersed black lines in TUI apps (same root cause as\n  Bug 1: cell height computed from fallback font before swap)",
          "is_bot": false,
          "headline": "v1.3.9: fix terminal rendering glitches that heal on resize",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-06-11T11:42:27Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "e038e74b23c07bca7b965a251def6a4846ade45c",
          "body": "The mobile terminal's hidden textarea was cleared after every input event,\npreventing Gboard autocorrect from operating on the existing text. When\nGboard replaced a misspelled word, the deletion was silently dropped and\nthe corrected text was appended on top, causing duplication.\n\nReplace the e.data\n[…]\nearing textarea after every character to let Gboard operate on it\n- Clear textarea on Enter/Tab (line submission) and refocus instead\n- Let browser handle Backspace natively; diff detects the deletion",
          "is_bot": false,
          "headline": "v1.3.8: fix Gboard autocorrect text duplication in mobile terminal",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-06-08T23:39:59Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "6bb0d00507d304490de68ed867a339879429c839",
          "body": null,
          "is_bot": false,
          "headline": "v1.3.7: fix tab flash animation showing underline on inactive tabs",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-06-04T11:52:12Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "86acb49cc4ec4f6c2885021fdd0bd99624fcbb20",
          "body": null,
          "is_bot": false,
          "headline": "v1.3.6: fix WebGL context loss recovery on tab backgrounding",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-06-02T14:06:40Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "9934b2b3c4aa88477a799044974082dc9545b15c",
          "body": "- Await document.fonts.ready before first write, then clear atlas + refresh\n  so glyphs are rasterised with the correct font instead of fallback\n- Add _resetWebGLAtlas() helper; clear texture atlas on resize, font-size\n  change, theme change, tab-switch font restore, and DPR change\n- Add _setupDPRLi\n[…]\nars highlighting\n- Refresh after Ctrl+C / Ctrl+Shift+C copy operations\n- Improve onContextLoss: null out webglAddon ref and refresh terminal to\n  repaint with fallback renderer\n- Bump version to 1.3.5",
          "is_bot": false,
          "headline": "v1.3.5: fix WebGL renderer blank cell artefacts",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-06-01T10:39:36Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "00828ba32bda56809ba7c787b07376aa683e3bdb",
          "body": null,
          "is_bot": false,
          "headline": "bump version to 1.3.4",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-28T14:59:29Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "8d2f15cf81ce3a5c4e6655856072dce3147a9798",
          "body": null,
          "is_bot": false,
          "headline": "bump version to 1.3.3",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-28T02:04:47Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "114988f8943c20eefa84b53481b19db33c9ded6e",
          "body": "…r tab distribution\n\n- Add getClockwisePanels() for correct tab distribution across panels\n  (top-left → top-right → bottom-right → bottom-left for 2x2)\n- Fix open-tabs handler: set layout BEFORE syncing tabs so panels\n  exist before distributeTabsToPanels runs\n- Fix saveTabs emitting to server duri\n[…]\nropdown to show ALL tabs with panel badge\n  indicators for tabs in other panels\n- Add .tab-panel-badge CSS for panel indicators in dropdown\n- Server: don't broadcast tabs-sync when tabs array is empty",
          "is_bot": false,
          "headline": "Fix multi-panel tab sync, mobile panel preservation, and cross-browse…",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-28T02:01:18Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "1e2530fe296672f50539c86f42f9f449ae0f0969",
          "body": "…terminal init on failure\n\n- During syncTabsFromServer, switchTab is suppressed to prevent the\n  last-created tab becoming active. Previously it returned immediately,\n  which meant terminal wrappers never got the 'active' CSS class,\n  causing initTerminal to fail with 'container not visible'. Now\n  \n[…]\ninstead of showing a toast.\n  The container may not be visible during sync because the correct\n  tab hasn't been activated yet — it becomes visible after the\n  final switchTab call at the end of sync.",
          "is_bot": false,
          "headline": "fix: suppress switchTab during sync but keep wrappers visible, retry …",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-28T01:06:50Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "b38072f1a4a344d341fec8e3adf83c455a17fa9d",
          "body": "…cked active state\n\nWhen the server restarts, activeTabsByPanel is empty (in-memory). The first\nclient connecting would get empty active tab info, causing the last-created\ntab to be selected instead of the first tab. Now if no active tab is tracked\nfor a panel, the first tab in that panel defaults to active.",
          "is_bot": false,
          "headline": "fix: default active tab to first tab per panel when server has no tra…",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-28T00:28:51Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "b7b602076717bdde2f1ce155b66b030004d4b5eb",
          "body": "… truth\n\n1. Server now tracks active tab per panel (activeTabsByPanel) and\n   includes it in open-tabs event. New clients see which tab was last\n   active instead of defaulting to the last-created tab.\n\n2. syncTabsFromServer suppresses switchTab during tab creation, then\n   activates the correct tab\n[…]\nr the correct tab is activated.\n\n5. tab-manager: removeTab() cleans up activeTabsByPanel references.\n   setActiveTab/getActiveTab/getActiveTabsByPanel added to track\n   and query active tab per panel.",
          "is_bot": false,
          "headline": "fix: active tab sync, mobile dropdown, and server as single source of…",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-28T00:21:28Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "12a5f4fa403ee0b273de75e260261268d58ec8d4",
          "body": "3 fixes for tabs disappearing or duplicating:\n\n1. When server has 0 active sessions (restart), restoreTabs() now always\n   creates NEW connections instead of trying to join dead sessions. This\n   eliminates the 'Session expired' message — credentials from localStorage\n   are used to reconnect automa\n[…]\nprocessed after init.\n\n3. Prevent duplicate tabs by fixing the timing — deferred open-tabs\n   ensures syncTabsFromServer runs only after the layout system and\n   event listeners are fully initialized.",
          "is_bot": false,
          "headline": "fix: session restoration after server restart and prevent duplicate tabs",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-27T23:55:47Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "d621a9f594b6443ac134950bbee933803b63a529",
          "body": "1. Server: SSH/SFTP session death no longer leaves zombie tab entries.\n   Previously, when an SSH connection died on the server (remote shell\n   exited, network drop, etc.) while no browser clients were connected,\n   the openTabs entry was never cleaned up. On reconnect, the client\n   would try ssh-\n[…]\nmissed.\n\n3. (Previous commit) Credentials from localStorage are merged into\n   server-synced tab data so auto-reconnection works even when the\n   server stripped passwords/private keys from open-tabs.",
          "is_bot": false,
          "headline": "fix: sessions disappearing on page refresh — 3 root causes",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-27T23:18:27Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "9185718f9a5a2bbc5713bca281fe2d6e19716e33",
          "body": "…sion recovery\n\nWhen the server sends open-tabs on reconnect/refresh, it strips\npassword/privateKey from connectionData for security. This meant that\nif ssh-join failed (e.g. server restarted), the auto-reconnect path\ncouldn't re-establish the SSH connection because it had no credentials.\n\nNow syncT\n[…]\nalStorage (which\nsaves them locally with the full connectionData) into the server's\nstripped version, so auto-reconnection works correctly after a page\nrefresh even if the server has lost the session.",
          "is_bot": false,
          "headline": "fix: merge credentials from localStorage into server tab sync for ses…",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-27T17:51:08Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "c15543664c29dd2dafb6773d69a74d9d8790a1b1",
          "body": "…twork changes\n\n- Changed reconnectionAttempts from 5 to Infinity so the client never\n  stops trying to reconnect (backoff caps at 30s)\n- Added visibilitychange handler to trigger immediate reconnect when\n  the user returns to the tab\n- Improved disconnect/reconnect toast messages\n- Suppressed noisy\n[…]\ning the\nclient. With sticky sessions the SSH connection stays alive on the\nserver, but the client had no way back. Now it keeps trying and\nautomatically restores the session when connectivity returns.",
          "is_bot": false,
          "headline": "fix: make Socket.IO reconnect indefinitely so SSH sessions survive ne…",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-27T17:24:33Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "7e91810c8a1fdac9c1b5da2495ded01896388fee",
          "body": "…ter bug",
          "is_bot": false,
          "headline": "fix: remove selectionForeground to fix WebGL glyph cache block charac…",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-27T17:14:32Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "b3151909e2be3736b15dd9d407bbc1c1a19c608e",
          "body": "Remove redundant localhost-only check on /api/update endpoint.\nThe global auth middleware already protects all endpoints when a\npassword is set. Without a password, the admin has chosen open access,\nso blocking only updates was inconsistent with other endpoints.",
          "is_bot": false,
          "headline": "v1.3.2: fix remote update blocked without password",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-27T14:14:33Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "670855733e62a01c57cb4c09a843324ef05f9b97",
          "body": "- Bump default scrollback from 2000 to 10000 lines\n- Add scrollback as configurable setting in config.json (0-100000)\n- Add scrollback input to settings dialog (Terminal category)\n- Wire scrollback through server config, WS handler, and client terminal\n- Increase screen sync size limit from 256KB to\n[…]\nB for larger buffers\n- Redesign settings modal: desktop sidebar nav + 7 categories\n- Mobile: dropdown category selector, responsive layout\n- Fixed-height desktop settings modal with scrollable content",
          "is_bot": false,
          "headline": "v1.3.1 - configurable scrollback, categorized settings",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-27T11:33:51Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "f51851720e65396c8a5bc9e11f2d9042174fbfc6",
          "body": null,
          "is_bot": false,
          "headline": "v1.3.0 - fix tab duplication, spurious auth errors, update layout",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-27T10:53:25Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "0a17f473eea6c851bc30fc49b79def46b3ab584b",
          "body": "- Add sftp-join handler so SFTP sessions use join instead of new connection\n- createSFTPTab uses sftp-join when restoreSessionId is provided\n- Add sftp-joined client event handler\n- Suppress 'All configured authentication methods failed' toast when\n  reconnecting with stripped credentials (server omits password/privateKey)\n- Show user-friendly message when session expires without stored credentials\n- SSH error handler: only reconnect if credentials are available",
          "is_bot": false,
          "headline": "fix: prevent spurious auth error toast on second browser tab (v1.2.2)",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-27T10:43:32Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "432673bf90544645fdc6946e3814c82816c1e8b4",
          "body": "- Server is now the single source of truth for tabs on initial sync\n- Remove stale localStorage tabs not present on server\n- Skip localStorage restore when server tabs arrive first\n- Add 3s fallback timeout for unreachable servers\n- Break restore loop if server sync arrives mid-restore\n- Add removeTabLocally() for clean local-only tab cleanup",
          "is_bot": false,
          "headline": "fix: prevent cross-device tab duplication in sticky mode (v1.2.1)",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-27T10:32:48Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "2b15eb391ec30c8f026ba57ef0f4b02fa152a230",
          "body": "Root cause: multi-byte UTF-8 characters and ANSI escape sequences were\nbeing corrupted at chunk boundaries throughout the data pipeline.\n\nChanges:\n- Add @xterm/addon-unicode11 to client and server headless terminal.\n  Without Unicode 11 width rules, xterm.js used Unicode 6 widths,\n  causing cursor/e\n[…]\nunks to split at escape-safe\n  boundaries (before ESC bytes, not mid-sequence) instead of\n  arbitrary 32KB offsets.\n- Add Unicode11 addon script and loading in initTerminal().\n- Bump version to 1.2.0.",
          "is_bot": false,
          "headline": "fix: prevent xterm.js character dropping in TUI apps (OpenCode, etc.)",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-25T15:02:36Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "1481ef5acc0839537eb032331de65cc144d37941",
          "body": "- Always pass passphrase to puttygen via temp file (--old-passphrase <file>)\n  instead of inline argument, fixes 'cannot open mopycsa1988' error on all\n  puttygen versions that only accept file arguments\n- Prevent interactive puttygen prompts (which hang Node) by always providing\n  a passphrase file\n[…]\ntygen dependency for v2 encrypted keys\n- Fix double passphrase prompt - only re-prompt if user hasn't entered one\n- Better error messages: wrong passphrase, v3 format unsupported, decryption\n  failure",
          "is_bot": false,
          "headline": "v1.1.9: Fix PPK key conversion - passphrase file and v3 detection",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-25T10:50:08Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "af71930543a3ea7712704d2518e4e7a7489fdf95",
          "body": "…llback\n\n- Remove --old-passphrase-file fallback (only in puttygen 0.81+,\n  causes confusing errors on older versions that hide the real error)\n- Use --old-passphrase=VALUE as primary format (standard GNU long opt)\n- Fallback to space-separated --old-passphrase VALUE only if equals\n  format returns \n[…]\npt for passphrase on any passphrase-related error, not just\n  when passphrase field is empty\n- Propagate key conversion errors to client instead of silently\n  continuing with unconvertible key content",
          "is_bot": false,
          "headline": "v1.1.8: Fix PPK passphrase handling - remove --old-passphrase-file fa…",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-25T10:06:23Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "969b944082402a72b7d7d158796e8c7251c836e6",
          "body": "- Fix puttygen --old-passphrase argument parsing (use equals sign format\n  to prevent passphrase being misinterpreted as filename)\n- Add --old-passphrase-file fallback for older puttygen versions\n- Prompt for passphrase when uploading encrypted key without one\n- Propagate key conversion errors to client instead of silently failing\n- Add onCancel callback to password modal for cancel handling",
          "is_bot": false,
          "headline": "v1.1.7: Fix encrypted PPK key passphrase handling",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-25T09:54:58Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "41884f36885d344fcf029c78dfbcbcd85a2f60d2",
          "body": "- Auto-convert PPK keys to OpenSSH at connection time (ssh-manager, sftp-manager)\n- Support encrypted PPK files with passphrase via puttygen --old-passphrase\n- Add convertKeyIfNeeded() utility for server-side PPK conversion\n- Pass passphrase through frontend key upload and REST convert-key endpoint\n- Auto-convert PPK keys when connecting from bookmarks (connectFromBookmark, openSFTPFromBookmark)\n- Fix case-insensitive PPK detection in offline fallback (PuTTY-User-Key-File regex)",
          "is_bot": false,
          "headline": "feat: add PPK key support for SSH connections, bump to 1.1.6",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-25T09:28:41Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "b23e1655a4e2783b6110c47d34000bd3fb52bb81",
          "body": "…obile input composition handling\n\n- Remove terminal.onBinary handler that was causing double-sends of\n  input on desktop (phantom characters in opencode input)\n- Add max toast limit (5) and deduplication to showToast()\n- Suppress socket connect/disconnect toasts during app update\n- Add max-height/overflow to toast container CSS\n- Fix mobile composition handling: check e.isComposing, e.inputType,\n  and deduplicate input vs compositionend (Gboard fix)\n- Bump version to 1.1.5",
          "is_bot": false,
          "headline": "fix: remove duplicate onBinary handler, fix toast stacking, improve m…",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-25T08:22:30Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "76065aae2144688f7375517298006bb859c3344d",
          "body": null,
          "is_bot": false,
          "headline": "bump version to 1.1.4",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-24T11:23:53Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "ff4cafeed3b4dfeb53351433f6cbf05bf3b79dcf",
          "body": "…, use readAsDataURL for fast base64 encoding",
          "is_bot": false,
          "headline": "fix: multi-file upload - append input to DOM for reliable file picker…",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-24T10:59:52Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "d44432ba9cafb8f85119139377809a97b7e4aad8",
          "body": "…ploads, and cancel button\n\n- Fix large file SFTP crash by switching from single-message base64 to\n  chunked transfer protocol (1MB chunks with backpressure)\n- Fix Node.js 24+ crash (util.isDate removed) with polyfill in server entry\n- Add upload/download progress bar with percentage, size, and file\n[…]\nloop, destroys\n  server-side stream, clears download state)\n- Increase Socket.IO maxHttpBufferSize to 5MB for chunk payloads\n- Stream downloads in 1MB chunks instead of buffering entire file in memory",
          "is_bot": false,
          "headline": "feat: chunked SFTP transfers, upload progress, multi-file/drag-drop u…",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-24T10:59:07Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "e4a1c8c5ace2ce5b9c67d0d7d33852fd19723379",
          "body": "… PowerShell (Admin)",
          "is_bot": false,
          "headline": "docs: add getsshift.dev badge, simplify Windows one-liner, relabel as…",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-24T10:01:41Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "9c302b9cafdcf76a5bc35fa51d57b4cd39f1eb3f",
          "body": null,
          "is_bot": false,
          "headline": "bump version to 1.1.3",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-21T14:10:35Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "c7a302e67c089c5d978f3750068880701bc19a82",
          "body": null,
          "is_bot": false,
          "headline": "bump version to 1.1.2",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-21T11:36:51Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "6625fbe5d9dc2ee9ec305cb8bf4c791629d615ed",
          "body": "…t (fixes arm64 crash)",
          "is_bot": false,
          "headline": "dockerfile: use npm install --package-lock-only instead of pnpm impor…",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-21T11:31:45Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "d8f018e65f9b7cf52c5708217a04f6be65720db6",
          "body": "…pm ci",
          "is_bot": false,
          "headline": "dockerfile: regenerate package-lock.json from pnpm-lock.yaml before n…",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-21T11:24:48Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "5957563b1514346618fb3ecac2fcf22ba35c84ca",
          "body": null,
          "is_bot": false,
          "headline": "regenerate package-lock.json to sync with package.json dependencies",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-21T11:19:43Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "027e2034b622c8ff6f132c9c19e264c6a2d17e8e",
          "body": "…n, add pnpm-lock.yaml to builder stage, bump label version",
          "is_bot": false,
          "headline": "fix dockerfile: use --omit=dev instead of deprecated --only=productio…",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-21T11:14:21Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "9c19a1414daeefffa8a97b3a03e9478e2a46bb08",
          "body": null,
          "is_bot": false,
          "headline": "v1.1.1: fix version and npm package name in publish workflow",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-21T11:08:33Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "bd0806252bf3eabdd240cb55feb027953a294d2d",
          "body": null,
          "is_bot": false,
          "headline": "add pnpm-lock.yaml and trust policy exclusion for semver@6.3.1",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-21T11:01:10Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "cc2c1ff1c25972d78358a95da8ea7b3a2016f326",
          "body": null,
          "is_bot": false,
          "headline": "ci: remove test step from CI workflow",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-21T10:53:10Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "dd7943c8895acb3e292f04637f71e9de7aba47ac",
          "body": "- Remove pnpm version override in GHA (use packageManager from package.json)\n- Fix CI job name capitalization\n- Fix Socket.dev badge URL to use badge.socket.dev",
          "is_bot": false,
          "headline": "v1.1.2: fix CI workflow and Socket badge",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-21T10:51:09Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "8eb4b42f5b6610c880c2adab9d7cb212410b2bbc",
          "body": "…ate endpoint\n\n- Add pnpm as primary package manager with 7-day minimum release age\n  gate and no-downgrade trust policy (pnpm-workspace.yaml, .npmrc)\n- Add Socket Firewall (sfw) integration for install-time malware scanning\n  (ci.yml, npm-publish.yml, package.json install:safe script)\n- Add gitleak\n[…]\ncy Bypass,\n  add network auth gate for non-localhost requests (system.js)\n- Exclude @lethevimlet/sshift from release age and trust policy gates\n- Add Socket.dev badge to README\n- Bump version to 1.1.1",
          "is_bot": false,
          "headline": "v1.1.1: add supply chain security (pnpm + gitleaks + sfw), harden upd…",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-21T10:43:54Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "0f460dda7bef05e7853a1c02a6df6266214c8e8a",
          "body": null,
          "is_bot": false,
          "headline": "Add CLI reference docs with --dev flag documentation",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-20T21:46:16Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "5ba3f585ea9da0cf8d6be6bda115d8a818b25cd4",
          "body": "…ity config",
          "is_bot": false,
          "headline": "Harden npm supply chain: pin dependency versions and add .npmrc secur…",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-20T21:39:50Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "35a6a67d1d8932dfcfc27ced52adb5d0fcced88b",
          "body": null,
          "is_bot": false,
          "headline": "Bump version to 1.1.0; separate dev PID file for side-by-side instances",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-20T20:56:39Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "5b01f2439922bd0adcd9750080eac1d07775f74f",
          "body": "…heduler)\n\n- Rename isSystemdManaged/systemdUnit to isServiceManaged/serviceManager/serviceUnit\n- Add launchd detection (macOS) and Task Scheduler detection (Windows)\n- Update shell script to use SERVICE_MANAGED/SERVICE_MANAGER/SERVICE_UNIT vars\n- Add launchctl stop/start restart flow for macOS in shell script\n- Fix orphaned catch block and backtick/quote syntax errors",
          "is_bot": false,
          "headline": "Add cross-platform service manager detection (systemd/launchd/Task Sc…",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-20T20:40:24Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "96ef9b4503d44b9f45baca061344e403e64b116d",
          "body": null,
          "is_bot": false,
          "headline": "Bumped version",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-20T20:17:07Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "098a0d08da7593f1535c0723e2e3202c10b0e238",
          "body": null,
          "is_bot": false,
          "headline": "Bump version to 1.0.20",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-20T20:02:35Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "a32ca79f12ccdb2647e3c95ff462283c266b963e",
          "body": null,
          "is_bot": false,
          "headline": "Fix ReferenceError: escape ${NPM_DELAY} in shell template literal",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-20T19:59:34Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "9e49b894900d7672158dec374b560f2227500ea2",
          "body": null,
          "is_bot": false,
          "headline": "Bumped version",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-20T19:40:00Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "ff363cd8ed6af54259d1a6421429584f6e0ecdea",
          "body": null,
          "is_bot": false,
          "headline": "Bump version to 1.0.17",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-20T19:34:41Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "c0153317a04a2c359832c46138162594f86814a0",
          "body": "…ttempts with 30s delay)\n\n- Use systemctl is-active as fallback when cgroup detection fails\n- Retry npm install up to 3 times with 30s delay for registry propagation\n- Same retry logic for Windows PowerShell script",
          "is_bot": false,
          "headline": "Fix systemd detection (systemctl is-active fallback) + npm retry (3 a…",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-20T19:18:48Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "f49e6fef80e6c6915ef1ddb069b16685937723f0",
          "body": null,
          "is_bot": false,
          "headline": "Testing GUI update",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-20T19:07:37Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "755bf8eb26e1796f0bcdc8e84b3df2aa8c5b9537",
          "body": null,
          "is_bot": false,
          "headline": "Bump version to 1.0.15",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-20T18:27:35Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "853e7ede1724ce4fa2dcd9d258a52bedcc2c8a76",
          "body": "…erpolation",
          "is_bot": false,
          "headline": "Fix ReferenceError: move systemd detection before script template int…",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-20T18:21:06Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "78b110273b6aa0b6922bb1d37cad26afee83c98f",
          "body": "Under systemd, the update script was being killed when systemctl\nstop/restart was called because it was in the same cgroup as the\nsshift service. Now uses systemd-run --scope to move the update\nscript to its own transient scope so it survives service restarts.\nAlso uses systemctl restart (not stop+start) for systemd path.",
          "is_bot": false,
          "headline": "Fix systemd update: spawn script in separate scope via systemd-run",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-20T18:13:56Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "60272cadeeb368e8c5e2d0503cbe803bd330fd07",
          "body": null,
          "is_bot": false,
          "headline": "Bump version to 1.0.12",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-20T18:07:59Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "59dd6711b7e055c2d3f1a506051fc5e7833cb5ee",
          "body": "…d of re-detecting\n\nThe shell script was trying to detect systemd independently but\nrunning in a different cgroup/namespace than the sshift process.\nNow passes isSystemdManaged and systemdUnit directly from the\nNode.js server (which correctly detects it via /proc/PID/cgroup)\nto the shell script as environment variables.",
          "is_bot": false,
          "headline": "Fix systemd detection in shell script - pass flag from Node.js instea…",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-20T18:05:18Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "654462b5754f1b3be4325d84d68cb647d8b4959e",
          "body": null,
          "is_bot": false,
          "headline": "Bump version to 1.0.10",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-20T17:59:36Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "ca6cf4e481c9e249010281e1ecdedd6b0715173c",
          "body": "- Detect systemd service and use systemctl stop/start instead of\n  process.exit(0) + exec, which caused a race condition where systemd\n  would restart the old version before npm install completed\n- When systemd-managed, keep the server running until the update\n  script stops it via systemctl, preventing dual-instances\n- Add Task Scheduler detection for Windows (PowerShell script)\n- Delay process exit to 500ms under systemd (script handles stop)",
          "is_bot": false,
          "headline": "Fix GUI update for systemd-managed installations",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-20T17:52:11Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "2e18e0a70d215a9ddd37c12075ee973b22179ba7",
          "body": null,
          "is_bot": false,
          "headline": "Bumped version to 1.0.8",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-20T15:42:57Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "ac315afaae00c902a83ccf86e641bf1091f8e2fb",
          "body": null,
          "is_bot": false,
          "headline": "Bumped version",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-20T15:23:55Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "be1546b34aa871ab491da2b052f59be57fb421c5",
          "body": null,
          "is_bot": false,
          "headline": "Bumped version",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-20T15:16:49Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "bed0a61508d40d8b8b193908484555aec49c740e",
          "body": null,
          "is_bot": false,
          "headline": "Bumped version",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-20T13:52:04Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "efce7f345656db8a6e31deec48b5ce48a2ed966b",
          "body": null,
          "is_bot": false,
          "headline": "Updated sshift bin pid logic",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-20T13:48:11Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "c70b6179b168dceda093eef8e13f6a29793aa505",
          "body": null,
          "is_bot": false,
          "headline": "Fixed manage sessions",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-20T12:06:50Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "4f718818e67c170625c88787f0c23da1806aec3c",
          "body": null,
          "is_bot": false,
          "headline": "Added a better cache busting strategy",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-20T11:55:28Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "fe8a0679b24b4683d2202885d5360dcc4fd4ab3d",
          "body": null,
          "is_bot": false,
          "headline": "Bumped version",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-19T14:56:48Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "e563c5a2a473ff678e59554318defac087c09ff9",
          "body": null,
          "is_bot": false,
          "headline": "Fixed ctrl+v double text issue",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-19T14:38:51Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "10afea8e2bbae39081e6dbafbb58eeb31e07f6b9",
          "body": null,
          "is_bot": false,
          "headline": "Added debug button in settings",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-19T14:22:37Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "ab31cd190d8733fb74b408974272fd915ddcb5c4",
          "body": null,
          "is_bot": false,
          "headline": "Made mobile term text size smaller and fixed copy context menu position",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-19T12:36:37Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "32c98f0528bb2b7f36f913e2ab2616e998b0839c",
          "body": null,
          "is_bot": false,
          "headline": "Added support for sc52 clipboard",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-19T12:24:58Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "79e0ce7ef838043defb4ac0cd89839d84aa8c2a9",
          "body": null,
          "is_bot": false,
          "headline": "Updated security dialogue and docs",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-19T11:37:53Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "e8276157205142ae38b09892fbc3ebe06d9d712f",
          "body": null,
          "is_bot": false,
          "headline": "Fixed an issue with mobile input",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-18T21:02:05Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "3e3dd8c09fd919aea3415a968eee22666cf25d36",
          "body": null,
          "is_bot": false,
          "headline": "Fixed mobile select scroll issue",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-18T16:51:15Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "c353f598ddfb2a29f3d3f51ff7f111ce9d452821",
          "body": null,
          "is_bot": false,
          "headline": "Bumped version",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-18T11:45:21Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "0475b7fb26b6017df79c30fc595edc492d55f962",
          "body": null,
          "is_bot": false,
          "headline": "Made mobile clipboard menu bigger",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-18T11:41:54Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "f1809f10e1217d0aacaac3c37e93ee0cb44d4672",
          "body": null,
          "is_bot": false,
          "headline": "Fixed clipboard refactor issues",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-18T11:36:11Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "361027745c085c1f27fa4e83de7850a3e7a7260a",
          "body": null,
          "is_bot": false,
          "headline": "Fixed paste issues",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-18T11:21:58Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "c87f692ccd2906dd8ff376db80bf6a8cdc7973e9",
          "body": null,
          "is_bot": false,
          "headline": "Fixed issue with update button",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-14T15:18:41Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "18afe14a9de98d6647bffa895be77049a10bf06c",
          "body": null,
          "is_bot": false,
          "headline": "Bumped version",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-13T15:29:13Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "85356bae33aaa3d5c6727340afbc1c4d3b8b3f9f",
          "body": null,
          "is_bot": false,
          "headline": "Fixed gitaction cleanup",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-13T15:24:13Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "c13140a9543956f02c5c1e852f8fd02c3d5addcd",
          "body": null,
          "is_bot": false,
          "headline": "Bumped version",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-13T15:03:51Z",
          "body_truncated": false,
          "is_coding_agent": false
        }
      ],
      "releases_count": 0,
      "commits_last_year": 244,
      "latest_release_at": null,
      "latest_release_tag": null,
      "releases_from_tags": false,
      "days_since_last_push": 9,
      "active_weeks_last_year": 12,
      "days_since_latest_release": null,
      "mean_days_between_releases": null
    },
    "community": {
      "has_readme": true,
      "has_license": true,
      "has_description": true,
      "has_contributing": true,
      "health_percentage": 57,
      "has_issue_template": false,
      "has_code_of_conduct": false,
      "has_pull_request_template": false
    },
    "ecosystem": {
      "packages": [
        {
          "name": "@lethevimlet/sshift",
          "exists": true,
          "license": "MIT",
          "keywords": [
            "ssh",
            "sftp",
            "terminal",
            "web",
            "xterm",
            "web-terminal",
            "ssh-client",
            "sftp-client",
            "browser-terminal"
          ],
          "ecosystem": "npm",
          "matches_repo": true,
          "registry_url": "https://www.npmjs.com/package/@lethevimlet/sshift",
          "is_deprecated": false,
          "latest_version": "1.6.1",
          "repository_url": "https://github.com/lethevimlet/sshift",
          "versions_count": 77,
          "total_downloads": null,
          "dependents_count": null,
          "deprecation_note": null,
          "maintainers_count": 1,
          "monthly_downloads": 2001,
          "first_published_at": "2026-04-08T21:01:07.835000Z",
          "latest_published_at": "2026-07-22T16:15:55.578000Z",
          "latest_version_yanked": null,
          "days_since_latest_publish": 9
        }
      ]
    },
    "popularity": {
      "forks": 3,
      "stars": 35,
      "watchers": 0,
      "fork_history": {
        "days": [
          {
            "date": "2026-04-05",
            "count": 1
          },
          {
            "date": "2026-04-27",
            "count": 1
          },
          {
            "date": "2026-05-05",
            "count": 1
          }
        ],
        "complete": true,
        "collected": 3,
        "total_forks": 3
      },
      "star_history": null,
      "open_issues_and_prs": 0
    },
    "ai_readiness": {
      "has_nix": false,
      "example_dirs": [],
      "has_llms_txt": false,
      "has_dockerfile": true,
      "has_mcp_signal": false,
      "bootstrap_files": [],
      "api_schema_files": [],
      "has_devcontainer": false,
      "typecheck_configs": [],
      "toolchain_manifests": [],
      "largest_source_bytes": 497398,
      "source_files_sampled": 77,
      "oversized_source_files": 7,
      "agent_instruction_files": [
        "AGENTS.md"
      ],
      "agent_instruction_max_bytes": 9064
    },
    "dependencies": {
      "manifests": [
        "package.json"
      ],
      "advisories": {
        "error": null,
        "scope": "published_package",
        "source": "osv",
        "findings": [
          {
            "name": "uuid",
            "direct": true,
            "version": "9.0.0",
            "severity": "high",
            "ecosystem": "npm",
            "cvss_score": 7.5,
            "advisory_ids": [
              "GHSA-w5hq-g745-h8pq"
            ],
            "fixed_version": "13.0.1",
            "advisory_count": 1,
            "oldest_advisory_days": 100
          },
          {
            "name": "body-parser",
            "direct": false,
            "version": "1.20.1",
            "severity": "high",
            "ecosystem": "npm",
            "cvss_score": 7.5,
            "advisory_ids": [
              "GHSA-qwcr-r2fm-qrc7",
              "GHSA-v422-hmwv-36x6"
            ],
            "fixed_version": "2.3.0",
            "advisory_count": 2,
            "oldest_advisory_days": 689
          },
          {
            "name": "engine.io",
            "direct": false,
            "version": "6.5.5",
            "severity": "high",
            "ecosystem": "npm",
            "cvss_score": 7.5,
            "advisory_ids": [
              "GHSA-r635-g3xr-vw7x"
            ],
            "fixed_version": "6.6.7",
            "advisory_count": 1,
            "oldest_advisory_days": 11
          },
          {
            "name": "path-to-regexp",
            "direct": false,
            "version": "0.1.7",
            "severity": "high",
            "ecosystem": "npm",
            "cvss_score": 7.5,
            "advisory_ids": [
              "GHSA-37ch-88jc-xwx2",
              "GHSA-9wv6-86v2-598j",
              "GHSA-rhx6-c78j-4q9w"
            ],
            "fixed_version": "8.0.0",
            "advisory_count": 3,
            "oldest_advisory_days": 690
          },
          {
            "name": "ws",
            "direct": false,
            "version": "8.17.1",
            "severity": "high",
            "ecosystem": "npm",
            "cvss_score": 7.5,
            "advisory_ids": [
              "GHSA-58qx-3vcg-4xpx",
              "GHSA-96hv-2xvq-fx4p"
            ],
            "fixed_version": "8.21.0",
            "advisory_count": 2,
            "oldest_advisory_days": 74
          },
          {
            "name": "express",
            "direct": true,
            "version": "4.18.2",
            "severity": "moderate",
            "ecosystem": "npm",
            "cvss_score": 6.1,
            "advisory_ids": [
              "GHSA-qw6h-vgh9-j6wx",
              "GHSA-rv95-896h-c2vc"
            ],
            "fixed_version": "5.0.0-beta.3",
            "advisory_count": 2,
            "oldest_advisory_days": 858
          },
          {
            "name": "qs",
            "direct": false,
            "version": "6.11.0",
            "severity": "moderate",
            "ecosystem": "npm",
            "cvss_score": 5.3,
            "advisory_ids": [
              "GHSA-6rw7-vpxm-498p",
              "GHSA-w7fw-mjwx-w883"
            ],
            "fixed_version": "6.14.2",
            "advisory_count": 2,
            "oldest_advisory_days": 213
          },
          {
            "name": "send",
            "direct": false,
            "version": "0.18.0",
            "severity": "moderate",
            "ecosystem": "npm",
            "cvss_score": 5,
            "advisory_ids": [
              "GHSA-m6fv-jmcg-4jfg"
            ],
            "fixed_version": "0.19.0",
            "advisory_count": 1,
            "oldest_advisory_days": 689
          },
          {
            "name": "serve-static",
            "direct": false,
            "version": "1.15.0",
            "severity": "moderate",
            "ecosystem": "npm",
            "cvss_score": 5,
            "advisory_ids": [
              "GHSA-cm22-4g7w-348p"
            ],
            "fixed_version": "2.1.0",
            "advisory_count": 1,
            "oldest_advisory_days": 689
          },
          {
            "name": "cookie",
            "direct": false,
            "version": "0.4.2",
            "severity": "low",
            "ecosystem": "npm",
            "cvss_score": null,
            "advisory_ids": [
              "GHSA-pxg6-pf52-xh8x"
            ],
            "fixed_version": "0.7.0",
            "advisory_count": 1,
            "oldest_advisory_days": 665
          },
          {
            "name": "cookie",
            "direct": false,
            "version": "0.5.0",
            "severity": "low",
            "ecosystem": "npm",
            "cvss_score": null,
            "advisory_ids": [
              "GHSA-pxg6-pf52-xh8x"
            ],
            "fixed_version": "0.7.0",
            "advisory_count": 1,
            "oldest_advisory_days": 665
          }
        ],
        "collected": true,
        "malicious": [],
        "truncated": false,
        "by_severity": {
          "low": 2,
          "high": 5,
          "moderate": 4
        },
        "advisory_count": 17,
        "affected_count": 11,
        "assessed_count": 132,
        "malicious_count": 0,
        "assessed_package": "npm:@lethevimlet/sshift@1.6.1",
        "unassessed_count": 0,
        "direct_affected_count": 2
      },
      "ecosystems": [
        "npm"
      ],
      "dependencies": [
        {
          "name": "@xterm/addon-fit",
          "manifest": "package.json",
          "ecosystem": "npm",
          "version_constraint": "0.11.0"
        },
        {
          "name": "@xterm/addon-image",
          "manifest": "package.json",
          "ecosystem": "npm",
          "version_constraint": "0.9.0"
        },
        {
          "name": "@xterm/addon-search",
          "manifest": "package.json",
          "ecosystem": "npm",
          "version_constraint": "0.16.0"
        },
        {
          "name": "@xterm/addon-serialize",
          "manifest": "package.json",
          "ecosystem": "npm",
          "version_constraint": "0.14.0"
        },
        {
          "name": "@xterm/addon-unicode11",
          "manifest": "package.json",
          "ecosystem": "npm",
          "version_constraint": "0.9.0"
        },
        {
          "name": "@xterm/addon-web-links",
          "manifest": "package.json",
          "ecosystem": "npm",
          "version_constraint": "0.12.0"
        },
        {
          "name": "@xterm/addon-webgl",
          "manifest": "package.json",
          "ecosystem": "npm",
          "version_constraint": "0.19.0"
        },
        {
          "name": "@xterm/headless",
          "manifest": "package.json",
          "ecosystem": "npm",
          "version_constraint": "6.0.0"
        },
        {
          "name": "@xterm/xterm",
          "manifest": "package.json",
          "ecosystem": "npm",
          "version_constraint": "6.0.0"
        },
        {
          "name": "dotenv",
          "manifest": "package.json",
          "ecosystem": "npm",
          "version_constraint": "17.4.0"
        },
        {
          "name": "express",
          "manifest": "package.json",
          "ecosystem": "npm",
          "version_constraint": "4.18.2"
        },
        {
          "name": "httpolyglot",
          "manifest": "package.json",
          "ecosystem": "npm",
          "version_constraint": "0.1.2"
        },
        {
          "name": "lucide",
          "manifest": "package.json",
          "ecosystem": "npm",
          "version_constraint": "1.7.0"
        },
        {
          "name": "selfsigned",
          "manifest": "package.json",
          "ecosystem": "npm",
          "version_constraint": "5.5.0"
        },
        {
          "name": "socket.io",
          "manifest": "package.json",
          "ecosystem": "npm",
          "version_constraint": "4.7.2"
        },
        {
          "name": "ssh2",
          "manifest": "package.json",
          "ecosystem": "npm",
          "version_constraint": "1.15.0"
        },
        {
          "name": "uuid",
          "manifest": "package.json",
          "ecosystem": "npm",
          "version_constraint": "9.0.0"
        }
      ],
      "all_dependencies": {
        "error": "GitHub dependency-graph SBOM unavailable (404); the dependency graph may be disabled for this repository",
        "source": null,
        "packages": [],
        "collected": false,
        "truncated": false,
        "total_count": null,
        "direct_count": null,
        "indirect_count": null
      }
    },
    "maintainership": {
      "issues": {
        "open_prs": 0,
        "merged_prs": 1,
        "open_issues": 0,
        "closed_ratio": null,
        "closed_issues": 0,
        "closed_unmerged_prs": 0
      },
      "bus_factor": 1,
      "bot_contributors": 0,
      "top_contributors": [
        {
          "type": "User",
          "login": "lethevimlet",
          "commits": 244,
          "avatar_url": "https://avatars.githubusercontent.com/u/2963923?v=4"
        }
      ],
      "contributors_sampled": 1,
      "top_contributor_share": 1
    },
    "quality_signals": {
      "has_ci": true,
      "has_tests": true,
      "ci_workflows": [
        "ci.yml",
        "docker-publish.yml",
        "npm-publish.yml"
      ],
      "has_docs_dir": true,
      "linter_configs": [],
      "has_editorconfig": false,
      "has_linter_config": true,
      "has_precommit_config": true
    },
    "security_signals": {
      "lockfiles": [
        "package-lock.json",
        "pnpm-lock.yaml"
      ],
      "scorecard": {
        "checks": [
          {
            "name": "Binary-Artifacts",
            "score": 10,
            "reason": "no binaries found in the repo",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#binary-artifacts"
          },
          {
            "name": "Branch-Protection",
            "score": 0,
            "reason": "branch protection not enabled on development/release branches",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#branch-protection"
          },
          {
            "name": "CI-Tests",
            "score": null,
            "reason": "no pull request found",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#ci-tests"
          },
          {
            "name": "CII-Best-Practices",
            "score": 0,
            "reason": "no effort to earn an OpenSSF best practices badge detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#cii-best-practices"
          },
          {
            "name": "Code-Review",
            "score": 0,
            "reason": "Found 0/30 approved changesets -- score normalized to 0",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#code-review"
          },
          {
            "name": "Contributors",
            "score": 3,
            "reason": "project has 1 contributing companies or organizations -- score normalized to 3",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#contributors"
          },
          {
            "name": "Dangerous-Workflow",
            "score": 10,
            "reason": "no dangerous workflow patterns detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#dangerous-workflow"
          },
          {
            "name": "Dependency-Update-Tool",
            "score": 0,
            "reason": "no update tool detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#dependency-update-tool"
          },
          {
            "name": "Fuzzing",
            "score": 0,
            "reason": "project is not fuzzed",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#fuzzing"
          },
          {
            "name": "License",
            "score": 10,
            "reason": "license file detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#license"
          },
          {
            "name": "Maintained",
            "score": 10,
            "reason": "30 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 10",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#maintained"
          },
          {
            "name": "Packaging",
            "score": 10,
            "reason": "packaging workflow detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#packaging"
          },
          {
            "name": "Pinned-Dependencies",
            "score": 0,
            "reason": "dependency not pinned by hash detected -- score normalized to 0",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#pinned-dependencies"
          },
          {
            "name": "SAST",
            "score": 0,
            "reason": "no SAST tool detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#sast"
          },
          {
            "name": "Security-Policy",
            "score": 0,
            "reason": "security policy file not detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#security-policy"
          },
          {
            "name": "Signed-Releases",
            "score": null,
            "reason": "no releases found",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#signed-releases"
          },
          {
            "name": "Token-Permissions",
            "score": 0,
            "reason": "detected GitHub workflow tokens with excessive permissions",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#token-permissions"
          },
          {
            "name": "Vulnerabilities",
            "score": 0,
            "reason": "25 existing vulnerabilities detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#vulnerabilities"
          }
        ],
        "commit": "d551d43a298c091d10e0e373670271f3ba075fa8",
        "ran_at": "2026-08-01T00:33:56Z",
        "aggregate_score": 3.5,
        "scorecard_version": "v5.5.0"
      },
      "has_codeql_workflow": false,
      "has_security_policy": false,
      "has_dependabot_config": false
    },
    "contribution_flow": {
      "collected": true,
      "ci_last_run_at": "2026-07-22T16:17:16Z",
      "oldest_open_prs": [],
      "last_merged_pr_at": "2026-04-07T12:53:21Z",
      "ci_last_conclusion": "FAILURE",
      "oldest_open_issues": []
    }
  },
  "config": {
    "disabled_metrics": [],
    "disabled_categories": [],
    "disabled_components": {}
  },
  "source": {
    "url": "https://github.com/lethevimlet/sshift",
    "host": "github.com",
    "name": "sshift",
    "owner": "lethevimlet"
  },
  "metrics": {
    "overall": {
      "key": "overall",
      "band": "moderate",
      "name": "Overall health",
      "note": "The weighted overall 54 is calibrated to 56 on the published index scale (record calibration 2026-08-02).",
      "notes": [
        {
          "code": "overall_calibration",
          "params": {
            "raw": 54,
            "calibrated": 56,
            "calibration": "2026-08-02"
          }
        }
      ],
      "value": 56,
      "inputs": {
        "security": 35,
        "vitality": 39,
        "community": 49,
        "governance": 52,
        "calibration": "2026-08-02",
        "engineering": 91,
        "ai_readiness": 54,
        "weighted_overall_raw": 54
      },
      "components": []
    },
    "categories": [
      {
        "key": "vitality",
        "band": "weak",
        "name": "Vitality",
        "value": 39,
        "weight": 0.21,
        "metrics": [
          {
            "key": "development_activity",
            "band": "good",
            "name": "Development activity",
            "note": null,
            "notes": [],
            "value": 65,
            "inputs": {
              "commits_last_year": 244,
              "human_commit_share": 1,
              "days_since_last_push": 9,
              "active_weeks_last_year": 12
            },
            "components": [
              {
                "key": "push_recency",
                "name": "Push recency",
                "detail": "last push 9 days ago",
                "points": 28.8,
                "status": "partial",
                "details": [
                  {
                    "code": "push_recency",
                    "params": {
                      "days": 9
                    }
                  }
                ],
                "max_points": 36
              },
              {
                "key": "commit_cadence",
                "name": "Commit cadence",
                "detail": "12/52 weeks with commits",
                "points": 8.3,
                "status": "partial",
                "details": [
                  {
                    "code": "commit_cadence_weeks",
                    "params": {
                      "weeks": 12
                    }
                  }
                ],
                "max_points": 36
              },
              {
                "key": "commit_volume",
                "name": "Commit volume",
                "detail": "244 commits in the last year",
                "points": 18,
                "status": "met",
                "details": [
                  {
                    "code": "commits_last_year",
                    "params": {
                      "count": 244
                    }
                  }
                ],
                "max_points": 18
              },
              {
                "key": "openssf_scorecard_maintained",
                "name": "OpenSSF Scorecard: Maintained",
                "detail": "30 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 10",
                "points": 10,
                "status": "met",
                "details": [],
                "max_points": 10
              }
            ]
          },
          {
            "key": "release_discipline",
            "band": "critical",
            "name": "Release discipline",
            "note": "Excluded from scoring (no data or not applicable): OpenSSF Scorecard: Signed-Releases. Remaining weights renormalized.",
            "notes": [
              {
                "code": "excluded_no_data",
                "params": {
                  "components": [
                    "openssf_scorecard_signed_releases"
                  ]
                }
              },
              {
                "code": "weights_renormalized",
                "params": {}
              }
            ],
            "value": 1,
            "inputs": {
              "releases_count": 0
            },
            "components": [
              {
                "key": "ships_releases",
                "name": "Ships releases",
                "detail": "no releases published",
                "points": 0,
                "status": "missed",
                "details": [
                  {
                    "code": "no_releases_published",
                    "params": {}
                  }
                ],
                "max_points": 27
              },
              {
                "key": "release_recency",
                "name": "Release recency",
                "detail": "no releases",
                "points": 0,
                "status": "missed",
                "details": [
                  {
                    "code": "no_releases",
                    "params": {}
                  }
                ],
                "max_points": 36
              },
              {
                "key": "release_cadence",
                "name": "Release cadence",
                "detail": "no releases",
                "points": 0,
                "status": "missed",
                "details": [
                  {
                    "code": "no_releases",
                    "params": {}
                  }
                ],
                "max_points": 27
              },
              {
                "key": "openssf_scorecard_signed_releases",
                "name": "OpenSSF Scorecard: Signed-Releases",
                "detail": "no releases found",
                "points": 0,
                "status": "excluded",
                "details": [
                  {
                    "code": "no_data",
                    "params": {}
                  }
                ],
                "max_points": 10
              }
            ]
          },
          {
            "key": "abandonment",
            "band": "exceptional",
            "name": "Abandonment",
            "note": null,
            "notes": [],
            "value": 100,
            "inputs": {
              "cap": null,
              "state": "unverified",
              "guards": [],
              "signals": [],
              "red_flag": false,
              "multiplier_pct": 100,
              "declared_reason": null,
              "unverified_reason": "repository_too_young",
              "unanswered_open_prs": null,
              "unanswered_open_issues": null,
              "days_since_last_merged_pr": null,
              "days_since_last_human_commit": null,
              "days_since_last_human_commit_is_floor": false
            },
            "components": [
              {
                "key": "project_is_still_maintained",
                "name": "Project is still maintained",
                "detail": "maintenance record not established from the collected data",
                "points": 100,
                "status": "met",
                "details": [
                  {
                    "code": "abandonment_unverified",
                    "params": {}
                  }
                ],
                "max_points": 100
              }
            ]
          }
        ],
        "description": "Is the project alive — is code being written and are releases shipping?"
      },
      {
        "key": "community",
        "band": "weak",
        "name": "Community & Adoption",
        "value": 49,
        "weight": 0.17,
        "metrics": [
          {
            "key": "popularity",
            "band": "at_risk",
            "name": "Popularity & adoption",
            "note": null,
            "notes": [],
            "value": 27,
            "inputs": {
              "forks": 3,
              "stars": 35,
              "watchers": 0,
              "growth_state": "unverified",
              "growth_factor_pct": 100,
              "growth_unverified_reason": "no_history"
            },
            "components": [
              {
                "key": "stars",
                "name": "Stars",
                "detail": "35 stars",
                "points": 24.8,
                "status": "partial",
                "details": [
                  {
                    "code": "stars",
                    "params": {
                      "count": 35
                    }
                  }
                ],
                "max_points": 60
              },
              {
                "key": "forks",
                "name": "Forks",
                "detail": "3 forks",
                "points": 2.5,
                "status": "partial",
                "details": [
                  {
                    "code": "forks",
                    "params": {
                      "count": 3
                    }
                  }
                ],
                "max_points": 25
              },
              {
                "key": "watchers",
                "name": "Watchers",
                "detail": "0 watchers",
                "points": 0,
                "status": "missed",
                "details": [
                  {
                    "code": "watchers",
                    "params": {
                      "count": 0
                    }
                  }
                ],
                "max_points": 15
              }
            ]
          },
          {
            "key": "community_health",
            "band": "good",
            "name": "Community health",
            "note": null,
            "notes": [],
            "value": 70,
            "inputs": {
              "has_readme": true,
              "has_license": true,
              "readme_badges": null,
              "has_contributing": true,
              "has_issue_template": false,
              "has_code_of_conduct": false,
              "readme_badge_services": [],
              "has_pull_request_template": false
            },
            "components": [
              {
                "key": "readme",
                "name": "README",
                "detail": null,
                "points": 22.5,
                "status": "met",
                "details": [],
                "max_points": 22.5
              },
              {
                "key": "license",
                "name": "License",
                "detail": "recognized license (MIT)",
                "points": 22.5,
                "status": "met",
                "details": [
                  {
                    "code": "license_standard",
                    "params": {}
                  },
                  {
                    "code": "license_spdx",
                    "params": {
                      "spdx": "MIT"
                    }
                  }
                ],
                "max_points": 22.5
              },
              {
                "key": "contributing_guide",
                "name": "CONTRIBUTING guide",
                "detail": null,
                "points": 18,
                "status": "met",
                "details": [],
                "max_points": 18
              },
              {
                "key": "code_of_conduct",
                "name": "Code of conduct",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 13.5
              },
              {
                "key": "issue_template",
                "name": "Issue template",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 7.2
              },
              {
                "key": "pr_template",
                "name": "PR template",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 6.3
              }
            ]
          },
          {
            "key": "ecosystem_adoption",
            "band": "moderate",
            "name": "Ecosystem adoption (downloads)",
            "note": "Excluded from scoring (no data or not applicable): Registry dependents. Remaining weights renormalized.",
            "notes": [
              {
                "code": "excluded_no_data",
                "params": {
                  "components": [
                    "registry_dependents"
                  ]
                }
              },
              {
                "code": "weights_renormalized",
                "params": {}
              }
            ],
            "value": 55,
            "inputs": {
              "packages": [
                "@lethevimlet/sshift"
              ],
              "dependents": null,
              "ecosystems": "npm",
              "total_downloads": null,
              "monthly_downloads": 2001
            },
            "components": [
              {
                "key": "monthly_downloads",
                "name": "Monthly downloads",
                "detail": "2,001 downloads/month across npm",
                "points": 44,
                "status": "partial",
                "details": [
                  {
                    "code": "downloads_monthly",
                    "params": {
                      "count": 2001,
                      "ecosystems": "npm"
                    }
                  }
                ],
                "max_points": 80
              },
              {
                "key": "registry_dependents",
                "name": "Registry dependents",
                "detail": "not reported by this ecosystem",
                "points": 0,
                "status": "excluded",
                "details": [
                  {
                    "code": "not_reported_by_this_ecosystem",
                    "params": {}
                  }
                ],
                "max_points": 20
              }
            ]
          }
        ],
        "description": "Does the project have users, downloads, attention, and a welcoming setup for contributors?"
      },
      {
        "key": "governance",
        "band": "moderate",
        "name": "Sustainability & Governance",
        "value": 52,
        "weight": 0.23,
        "metrics": [
          {
            "key": "maintainer_resilience",
            "band": "critical",
            "name": "Maintainer resilience (bus factor)",
            "note": null,
            "notes": [],
            "value": 13,
            "inputs": {
              "bus_factor": 1,
              "contributors_sampled": 1,
              "top_contributor_share": 1
            },
            "components": [
              {
                "key": "bus_factor",
                "name": "Bus factor",
                "detail": "1 contributor(s) cover half of all commits",
                "points": 9,
                "status": "partial",
                "details": [
                  {
                    "code": "bus_factor",
                    "params": {
                      "count": 1
                    }
                  }
                ],
                "max_points": 54
              },
              {
                "key": "commit_distribution",
                "name": "Commit distribution",
                "detail": "top contributor authored 100% of commits",
                "points": 0,
                "status": "missed",
                "details": [
                  {
                    "code": "top_contributor_share",
                    "params": {
                      "share": 100
                    }
                  }
                ],
                "max_points": 22.5
              },
              {
                "key": "contributor_breadth",
                "name": "Contributor breadth",
                "detail": "1 contributors",
                "points": 1.4,
                "status": "partial",
                "details": [
                  {
                    "code": "contributors_sampled",
                    "params": {
                      "count": 1
                    }
                  }
                ],
                "max_points": 13.5
              },
              {
                "key": "openssf_scorecard_contributors",
                "name": "OpenSSF Scorecard: Contributors",
                "detail": "project has 1 contributing companies or organizations -- score normalized to 3",
                "points": 3,
                "status": "partial",
                "details": [],
                "max_points": 10
              }
            ]
          },
          {
            "key": "responsiveness",
            "band": "good",
            "name": "Issue & PR responsiveness",
            "note": "Excluded from scoring (no data or not applicable): Issue resolution, Newcomer PR acceptance. Remaining weights renormalized.",
            "notes": [
              {
                "code": "excluded_no_data",
                "params": {
                  "components": [
                    "issue_resolution",
                    "newcomer_pr_acceptance"
                  ]
                }
              },
              {
                "code": "weights_renormalized",
                "params": {}
              }
            ],
            "value": 67,
            "inputs": {
              "merged_prs": 1,
              "open_issues": 0,
              "closed_issues": 0,
              "prs_merged_7d": null,
              "prs_decided_7d": null,
              "prs_merged_30d": null,
              "prs_decided_30d": null,
              "issue_closed_ratio": null,
              "closed_unmerged_prs": 0,
              "first_time_authors_30d": null,
              "first_time_prs_merged_30d": null,
              "first_time_prs_decided_30d": null
            },
            "components": [
              {
                "key": "issue_resolution",
                "name": "Issue resolution",
                "detail": "no issues or no data",
                "points": 0,
                "status": "excluded",
                "details": [
                  {
                    "code": "no_issues_or_data",
                    "params": {}
                  }
                ],
                "max_points": 42
              },
              {
                "key": "pr_acceptance",
                "name": "PR acceptance",
                "detail": "1/1 decided PRs merged",
                "points": 30,
                "status": "met",
                "details": [
                  {
                    "code": "decided_prs_merged",
                    "params": {
                      "merged": 1,
                      "decided": 1
                    }
                  }
                ],
                "max_points": 30
              },
              {
                "key": "newcomer_pr_acceptance",
                "name": "Newcomer PR acceptance",
                "detail": "no first-time contributor's PR decided in 30d",
                "points": 0,
                "status": "excluded",
                "details": [
                  {
                    "code": "no_newcomer_prs",
                    "params": {
                      "days": 30
                    }
                  }
                ],
                "max_points": 13
              },
              {
                "key": "openssf_scorecard_code_review",
                "name": "OpenSSF Scorecard: Code-Review",
                "detail": "Found 0/30 approved changesets -- score normalized to 0",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 15
              }
            ]
          },
          {
            "key": "stewardship",
            "band": "weak",
            "name": "Ownership & stewardship",
            "note": "Excluded from scoring (no data or not applicable): Verified domain. Remaining weights renormalized.",
            "notes": [
              {
                "code": "excluded_no_data",
                "params": {
                  "components": [
                    "verified_domain"
                  ]
                }
              },
              {
                "code": "weights_renormalized",
                "params": {}
              }
            ],
            "value": 47,
            "inputs": {
              "followers": 10,
              "owner_type": "User",
              "is_verified": null,
              "owner_login": "lethevimlet",
              "public_repos": 12,
              "account_age_days": 4987
            },
            "components": [
              {
                "key": "ownership_backing",
                "name": "Ownership backing",
                "detail": "personal (user) account",
                "points": 10,
                "status": "partial",
                "details": [
                  {
                    "code": "owner_personal",
                    "params": {}
                  }
                ],
                "max_points": 30
              },
              {
                "key": "verified_domain",
                "name": "Verified domain",
                "detail": "not applicable to user accounts",
                "points": 0,
                "status": "excluded",
                "details": [
                  {
                    "code": "not_applicable_to_user_accounts",
                    "params": {}
                  }
                ],
                "max_points": 20
              },
              {
                "key": "owner_reach",
                "name": "Owner reach",
                "detail": "10 followers of lethevimlet",
                "points": 7.5,
                "status": "partial",
                "details": [
                  {
                    "code": "owner_followers",
                    "params": {
                      "count": 10,
                      "login": "lethevimlet"
                    }
                  }
                ],
                "max_points": 25
              },
              {
                "key": "track_record",
                "name": "Track record",
                "detail": "12 public repos, account ~13 yr old",
                "points": 20.1,
                "status": "partial",
                "details": [
                  {
                    "code": "public_repos",
                    "params": {
                      "count": 12
                    }
                  },
                  {
                    "code": "account_age_years",
                    "params": {
                      "years": 13
                    }
                  }
                ],
                "max_points": 25
              }
            ]
          },
          {
            "key": "package_maintenance",
            "band": "exceptional",
            "name": "Package maintenance",
            "note": null,
            "notes": [],
            "value": 100,
            "inputs": {
              "packages": [
                "@lethevimlet/sshift"
              ],
              "ecosystems": "npm",
              "any_deprecated": false,
              "min_days_since_publish": 9
            },
            "components": [
              {
                "key": "published_resolvable",
                "name": "Published & resolvable",
                "detail": "1 package(s) on npm",
                "points": 25,
                "status": "met",
                "details": [
                  {
                    "code": "packages_published",
                    "params": {
                      "count": 1,
                      "ecosystems": "npm"
                    }
                  }
                ],
                "max_points": 25
              },
              {
                "key": "publish_recency",
                "name": "Publish recency",
                "detail": "latest publish 9 days ago",
                "points": 35,
                "status": "met",
                "details": [
                  {
                    "code": "publish_recency",
                    "params": {
                      "days": 9
                    }
                  }
                ],
                "max_points": 35
              },
              {
                "key": "version_history",
                "name": "Version history",
                "detail": "77 published versions",
                "points": 20,
                "status": "met",
                "details": [
                  {
                    "code": "published_versions",
                    "params": {
                      "count": 77
                    }
                  }
                ],
                "max_points": 20
              },
              {
                "key": "not_deprecated",
                "name": "Not deprecated",
                "detail": "active, not deprecated or yanked",
                "points": 20,
                "status": "met",
                "details": [
                  {
                    "code": "package_not_deprecated",
                    "params": {}
                  }
                ],
                "max_points": 20
              }
            ]
          }
        ],
        "description": "Will the project survive its people — bus factor, responsiveness, who backs it, and package upkeep?"
      },
      {
        "key": "engineering",
        "band": "excellent",
        "name": "Engineering Quality",
        "value": 91,
        "weight": 0.19,
        "metrics": [
          {
            "key": "engineering_practices",
            "band": "excellent",
            "name": "Engineering practices",
            "note": "Excluded from scoring (no data or not applicable): OpenSSF Scorecard: CI-Tests. Remaining weights renormalized.",
            "notes": [
              {
                "code": "excluded_no_data",
                "params": {
                  "components": [
                    "openssf_scorecard_ci_tests"
                  ]
                }
              },
              {
                "code": "weights_renormalized",
                "params": {}
              }
            ],
            "value": 92,
            "inputs": {
              "has_ci": true,
              "has_tests": true,
              "has_editorconfig": false,
              "has_linter_config": true,
              "has_precommit_config": true
            },
            "components": [
              {
                "key": "ci_workflows",
                "name": "CI workflows",
                "detail": "3 workflow(s)",
                "points": 24,
                "status": "met",
                "details": [
                  {
                    "code": "ci_workflows",
                    "params": {
                      "count": 3
                    }
                  }
                ],
                "max_points": 24
              },
              {
                "key": "tests_present",
                "name": "Tests present",
                "detail": null,
                "points": 24,
                "status": "met",
                "details": [],
                "max_points": 24
              },
              {
                "key": "linter_config",
                "name": "Linter config",
                "detail": null,
                "points": 16,
                "status": "met",
                "details": [],
                "max_points": 16
              },
              {
                "key": "pre_commit_hooks",
                "name": "Pre-commit hooks",
                "detail": null,
                "points": 9.6,
                "status": "met",
                "details": [],
                "max_points": 9.6
              },
              {
                "key": "editorconfig",
                "name": ".editorconfig",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 6.4
              },
              {
                "key": "openssf_scorecard_ci_tests",
                "name": "OpenSSF Scorecard: CI-Tests",
                "detail": "no pull request found",
                "points": 0,
                "status": "excluded",
                "details": [
                  {
                    "code": "no_data",
                    "params": {}
                  }
                ],
                "max_points": 20
              }
            ]
          },
          {
            "key": "documentation",
            "band": "excellent",
            "name": "Documentation",
            "note": null,
            "notes": [],
            "value": 90,
            "inputs": {
              "topics": [],
              "has_wiki": true,
              "homepage": "https://getsshift.dev",
              "has_readme": true,
              "has_docs_dir": true,
              "has_description": true
            },
            "components": [
              {
                "key": "readme",
                "name": "README",
                "detail": null,
                "points": 30,
                "status": "met",
                "details": [],
                "max_points": 30
              },
              {
                "key": "documentation_directory",
                "name": "Documentation directory",
                "detail": null,
                "points": 25,
                "status": "met",
                "details": [],
                "max_points": 25
              },
              {
                "key": "documentation_homepage_site",
                "name": "Documentation / homepage site",
                "detail": "https://getsshift.dev",
                "points": 15,
                "status": "met",
                "details": [],
                "max_points": 15
              },
              {
                "key": "repository_description",
                "name": "Repository description",
                "detail": null,
                "points": 10,
                "status": "met",
                "details": [],
                "max_points": 10
              },
              {
                "key": "topics",
                "name": "Topics",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 10
              },
              {
                "key": "wiki",
                "name": "Wiki",
                "detail": null,
                "points": 10,
                "status": "met",
                "details": [],
                "max_points": 10
              }
            ]
          }
        ],
        "description": "Are baseline engineering and documentation practices in place?"
      },
      {
        "key": "security",
        "band": "weak",
        "name": "Security",
        "value": 35,
        "weight": 0.16,
        "metrics": [
          {
            "key": "security_posture",
            "band": "weak",
            "name": "Security posture",
            "note": "Excluded from scoring (no data or not applicable): CI-Tests, Signed-Releases. Remaining weights renormalized.",
            "notes": [
              {
                "code": "excluded_no_data",
                "params": {
                  "components": [
                    "ci_tests",
                    "signed_releases"
                  ]
                }
              },
              {
                "code": "weights_renormalized",
                "params": {}
              }
            ],
            "value": 35,
            "inputs": {
              "source": "openssf_scorecard",
              "checks_evaluated": 16,
              "scorecard_version": "v5.5.0",
              "checks_inconclusive": 2,
              "scorecard_aggregate": 3.5
            },
            "components": [
              {
                "key": "binary_artifacts",
                "name": "Binary-Artifacts",
                "detail": "no binaries found in the repo",
                "points": 7.5,
                "status": "met",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "branch_protection",
                "name": "Branch-Protection",
                "detail": "branch protection not enabled on development/release branches",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "ci_tests",
                "name": "CI-Tests",
                "detail": "no pull request found",
                "points": 0,
                "status": "excluded",
                "details": [
                  {
                    "code": "no_data",
                    "params": {}
                  }
                ],
                "max_points": 2.5
              },
              {
                "key": "cii_best_practices",
                "name": "CII-Best-Practices",
                "detail": "no effort to earn an OpenSSF best practices badge detected",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 2.5
              },
              {
                "key": "code_review",
                "name": "Code-Review",
                "detail": "Found 0/30 approved changesets -- score normalized to 0",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "contributors",
                "name": "Contributors",
                "detail": "project has 1 contributing companies or organizations -- score normalized to 3",
                "points": 0.8,
                "status": "partial",
                "details": [],
                "max_points": 2.5
              },
              {
                "key": "dangerous_workflow",
                "name": "Dangerous-Workflow",
                "detail": "no dangerous workflow patterns detected",
                "points": 10,
                "status": "met",
                "details": [],
                "max_points": 10
              },
              {
                "key": "dependency_update_tool",
                "name": "Dependency-Update-Tool",
                "detail": "no update tool detected",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "fuzzing",
                "name": "Fuzzing",
                "detail": "project is not fuzzed",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 5
              },
              {
                "key": "license",
                "name": "License",
                "detail": "license file detected",
                "points": 2.5,
                "status": "met",
                "details": [],
                "max_points": 2.5
              },
              {
                "key": "maintained",
                "name": "Maintained",
                "detail": "30 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 10",
                "points": 7.5,
                "status": "met",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "packaging",
                "name": "Packaging",
                "detail": "packaging workflow detected",
                "points": 5,
                "status": "met",
                "details": [],
                "max_points": 5
              },
              {
                "key": "pinned_dependencies",
                "name": "Pinned-Dependencies",
                "detail": "dependency not pinned by hash detected -- score normalized to 0",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 5
              },
              {
                "key": "sast",
                "name": "SAST",
                "detail": "no SAST tool detected",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 5
              },
              {
                "key": "security_policy",
                "name": "Security-Policy",
                "detail": "security policy file not detected",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 5
              },
              {
                "key": "signed_releases",
                "name": "Signed-Releases",
                "detail": "no releases found",
                "points": 0,
                "status": "excluded",
                "details": [
                  {
                    "code": "no_data",
                    "params": {}
                  }
                ],
                "max_points": 7.5
              },
              {
                "key": "token_permissions",
                "name": "Token-Permissions",
                "detail": "detected GitHub workflow tokens with excessive permissions",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "vulnerabilities",
                "name": "Vulnerabilities",
                "detail": "25 existing vulnerabilities detected",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 7.5
              }
            ]
          },
          {
            "key": "dependency_advisories",
            "band": "weak",
            "name": "Dependency advisories",
            "note": "Matched the npm:@lethevimlet/sshift@1.6.1 runtime dependency closure — what installing the published package pulls in — 132 packages. Reachability is not analyzed.",
            "notes": [
              {
                "code": "advisories_scope_published",
                "params": {
                  "package": "npm:@lethevimlet/sshift@1.6.1",
                  "assessed": 132
                }
              },
              {
                "code": "advisories_reachability",
                "params": {}
              }
            ],
            "value": 36,
            "inputs": {
              "source": "osv",
              "advisories": 17,
              "affected_packages": 11,
              "assessed_packages": 132,
              "unassessed_packages": 0,
              "affected_by_severity": "high 5, moderate 4, low 2",
              "direct_affected_packages": 2
            },
            "components": [
              {
                "key": "direct_dependencies_free_of_known_advisories",
                "name": "Direct dependencies free of known advisories",
                "detail": "2 affected: uuid 9.0.0 (high 7.5), express 4.18.2 (moderate 6.1)",
                "points": 12.1,
                "status": "partial",
                "details": [
                  {
                    "code": "advisories_affected",
                    "params": {
                      "count": 2,
                      "packages": "uuid 9.0.0 (high 7.5), express 4.18.2 (moderate 6.1)"
                    }
                  }
                ],
                "max_points": 35
              },
              {
                "key": "indirect_dependencies_free_of_known_advisories",
                "name": "Indirect dependencies free of known advisories",
                "detail": "9 affected: body-parser 1.20.1 (high 7.5), engine.io 6.5.5 (high 7.5), path-to-regexp 0.1.7 (high 7.5), +6 more",
                "points": 5,
                "status": "partial",
                "details": [
                  {
                    "code": "advisories_affected",
                    "params": {
                      "count": 9,
                      "packages": "body-parser 1.20.1 (high 7.5), engine.io 6.5.5 (high 7.5), path-to-regexp 0.1.7 (high 7.5)"
                    }
                  },
                  {
                    "code": "advisories_affected_more",
                    "params": {
                      "count": 6
                    }
                  }
                ],
                "max_points": 25
              },
              {
                "key": "no_advisories_left_outstanding",
                "name": "No advisories left outstanding",
                "detail": "9 advisory-carrying package(s) unaddressed past 90 days; oldest published 858 days ago",
                "points": 18.6,
                "status": "partial",
                "details": [
                  {
                    "code": "advisories_stale",
                    "params": {
                      "days": 90,
                      "count": 9,
                      "oldest": 858
                    }
                  }
                ],
                "max_points": 40
              }
            ]
          },
          {
            "key": "malicious_dependencies",
            "band": "exceptional",
            "name": "Malicious dependencies",
            "note": null,
            "notes": [],
            "value": 100,
            "inputs": {
              "source": "osv",
              "meaning": "reported as a malicious package by the OpenSSF corpus; the remedy is removal or moving off the compromised name, never an upgrade of the same artifact. Versions the registry has since pulled are listed but not scored",
              "packages": [],
              "red_flag": false,
              "assessed_packages": 132,
              "malicious_packages": 0,
              "direct_malicious_packages": 0,
              "withdrawn_malicious_packages": 0,
              "installable_malicious_packages": 0
            },
            "components": [
              {
                "key": "no_dependency_reported_as_a_malicious_package",
                "name": "No dependency reported as a malicious package",
                "detail": "no dependency is reported as a malicious package",
                "points": 100,
                "status": "met",
                "details": [
                  {
                    "code": "no_malicious_dependencies",
                    "params": {}
                  }
                ],
                "max_points": 100
              }
            ]
          },
          {
            "key": "high_risk_jurisdiction_exposure",
            "band": "exceptional",
            "name": "High-Risk Jurisdiction Exposure",
            "note": "Only high-confidence self-published location evidence affects this multiplier. Ambiguous matches are review-only; country evidence is not proof of nationality, citizenship, legal registration, malicious intent, or sanctions status.",
            "notes": [
              {
                "code": "jurisdiction_evidence_limits",
                "params": {}
              }
            ],
            "value": 100,
            "inputs": {
              "meaning": "self-published location evidence; not nationality or citizenship",
              "red_flag": false,
              "exposures": [],
              "policy_countries": [
                "Russia",
                "Iran",
                "North Korea"
              ],
              "commit_weight_rule": {
                "min_commits": 50,
                "min_commit_share": 0.1
              },
              "review_only_matches": 0,
              "below_threshold_exposures": [],
              "assessed_self_published_locations": 2
            },
            "components": [
              {
                "key": "policy_exposure_multiplier",
                "name": "Policy exposure multiplier",
                "detail": "no confirmed policy-scope location match",
                "points": 100,
                "status": "met",
                "details": [
                  {
                    "code": "jurisdiction_no_match",
                    "params": {}
                  }
                ],
                "max_points": 100
              }
            ]
          }
        ],
        "description": "Are visible security and supply-chain practices strong, with no malicious dependency and no unresolved high-risk jurisdiction exposure?"
      },
      {
        "key": "ai_readiness",
        "band": "moderate",
        "name": "AI Readiness",
        "value": 54,
        "weight": 0.04,
        "metrics": [
          {
            "key": "ai_agent_context",
            "band": "good",
            "name": "Agent context & guidance",
            "note": null,
            "notes": [],
            "value": 72,
            "inputs": {
              "has_llms_txt": false,
              "legible_history_share": 0.51,
              "agent_instruction_files": [
                "AGENTS.md"
              ],
              "agent_instruction_max_bytes": 9064
            },
            "components": [
              {
                "key": "agent_instructions",
                "name": "Agent instructions",
                "detail": "AGENTS.md",
                "points": 45,
                "status": "met",
                "details": [
                  {
                    "code": "file_list",
                    "params": {
                      "files": "AGENTS.md"
                    }
                  }
                ],
                "max_points": 45
              },
              {
                "key": "machine_readable_docs_llms_txt",
                "name": "Machine-readable docs (llms.txt)",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 15
              },
              {
                "key": "legible_commit_history",
                "name": "Legible commit history",
                "detail": "51 of 100 human commits state their intent (structured subject or explanatory body)",
                "points": 27.2,
                "status": "partial",
                "details": [
                  {
                    "code": "legible_history",
                    "params": {
                      "legible": 51,
                      "sampled": 100
                    }
                  }
                ],
                "max_points": 40
              }
            ]
          },
          {
            "key": "ai_verify_loop",
            "band": "weak",
            "name": "Verify loop (build / test / typecheck)",
            "note": null,
            "notes": [],
            "value": 43,
            "inputs": {
              "has_nix": false,
              "has_tests": true,
              "lockfiles": [
                "package-lock.json",
                "pnpm-lock.yaml"
              ],
              "has_dockerfile": true,
              "typed_language": false,
              "bootstrap_files": [],
              "has_devcontainer": false,
              "has_linter_config": true,
              "typecheck_configs": [],
              "agent_commit_share": 0,
              "toolchain_manifests": [],
              "dependency_bot_commit_share": 0
            },
            "components": [
              {
                "key": "one_command_bootstrap",
                "name": "One-command bootstrap",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 18
              },
              {
                "key": "automated_tests",
                "name": "Automated tests",
                "detail": null,
                "points": 22,
                "status": "met",
                "details": [],
                "max_points": 22
              },
              {
                "key": "lint_format_config",
                "name": "Lint / format config",
                "detail": null,
                "points": 11,
                "status": "met",
                "details": [],
                "max_points": 11
              },
              {
                "key": "static_type_checking",
                "name": "Static type checking",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 11
              },
              {
                "key": "reproducible_environment",
                "name": "Reproducible environment",
                "detail": "Dockerfile, lockfile",
                "points": 10,
                "status": "met",
                "details": [
                  {
                    "code": "file_list",
                    "params": {
                      "files": "Dockerfile, lockfile"
                    }
                  }
                ],
                "max_points": 10
              },
              {
                "key": "demonstrated_agent_practice",
                "name": "Demonstrated agent practice",
                "detail": "no agent-authored commits among the last 100",
                "points": 0,
                "status": "missed",
                "details": [
                  {
                    "code": "no_agent_authored_commits",
                    "params": {
                      "sampled": 100
                    }
                  }
                ],
                "max_points": 10
              },
              {
                "key": "automated_maintenance",
                "name": "Automated maintenance",
                "detail": "no automated dependency updates observed",
                "points": 0,
                "status": "missed",
                "details": [
                  {
                    "code": "no_dependency_automation",
                    "params": {}
                  }
                ],
                "max_points": 8
              },
              {
                "key": "openssf_scorecard_pinned_dependencies",
                "name": "OpenSSF Scorecard: Pinned-Dependencies",
                "detail": "dependency not pinned by hash detected -- score normalized to 0",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 10
              }
            ]
          },
          {
            "key": "ai_code_legibility",
            "band": "moderate",
            "name": "Code legibility for models",
            "note": null,
            "notes": [],
            "value": 50,
            "inputs": {
              "primary_language": "JavaScript",
              "largest_source_bytes": 497398,
              "source_files_sampled": 77,
              "oversized_source_files": 7
            },
            "components": [
              {
                "key": "type_checkable_code",
                "name": "Type-checkable code",
                "detail": "JavaScript without a type-check config",
                "points": 0,
                "status": "missed",
                "details": [
                  {
                    "code": "no_typecheck_config_language",
                    "params": {
                      "language": "JavaScript"
                    }
                  }
                ],
                "max_points": 45
              },
              {
                "key": "manageable_file_sizes",
                "name": "Manageable file sizes",
                "detail": "7/77 source files over 60KB",
                "points": 50,
                "status": "partial",
                "details": [
                  {
                    "code": "oversized_source_files",
                    "params": {
                      "kb": 60,
                      "sampled": 77,
                      "oversized": 7
                    }
                  }
                ],
                "max_points": 55
              }
            ]
          }
        ],
        "description": "How well is the repo equipped to be developed and maintained with AI coding agents? Carries a deliberately small weight: agent tooling is a real maintenance signal, but its absence must never gate the top of the scale (calibration saturates at raw 91, so 100/100 remains reachable with AI Readiness at zero)."
      }
    ],
    "classification": {
      "top": [
        "library",
        "application"
      ],
      "labels": [
        "library",
        "network-service"
      ],
      "scores": {
        "library": 6,
        "network-service": 4
      },
      "primary": "library",
      "evidence": [
        {
          "tier": "distribution",
          "label": "library",
          "source": "registry:npm",
          "weight": 6
        },
        {
          "tier": "dependencies",
          "label": "network-service",
          "source": "dep:express",
          "weight": 4
        }
      ],
      "artifacts": [],
      "confidence": "medium",
      "host_extension": false,
      "runs_as_process": true,
      "consumed_by_code": true
    },
    "metrics_version": "2.5.0"
  },
  "warnings": [
    "Star history unavailable: GitHub GraphQL error: Resource not accessible by personal access token",
    "GitHub dependency-graph SBOM unavailable (404); the dependency graph may be disabled for this repository"
  ],
  "report_type": "repository",
  "generated_at": "2026-08-01T00:34:02.224350Z",
  "schema_version": "0.27.0",
  "badge_url": "https://raw.githubusercontent.com/inspect-software/badges/main/v1/l/lethevimlet/sshift.svg",
  "full_name": "lethevimlet/sshift",
  "license_state": "standard",
  "license_spdx": "MIT"
}

Оцінки — це сигнали, а не гарантії. Вони відображають публічно видимі практики на GitHub — це не аудит коду й не гарантія безпеки.

Відсутні дані виключаються, а ваги перенормовуються — нуль за відсутність ніколи не ставиться. Методологія версіонована й відкрита: метрики v2.5.0, схема v0.27.0 — повна методологія · вікі метрик.

Як окремий результат виглядає на тлі всього реєстру: сукупна статистикаnpm.