From 042c4d655090e0ee334208511ec63b6d909d4354 Mon Sep 17 00:00:00 2001 From: Dmitriy Petrov Date: Tue, 23 Jun 2026 09:32:39 +0300 Subject: [PATCH] Check manual-full UI text in smoke test --- .understand-anything/.understandignore | 27 + .understand-anything/config.json | 3 + .../intermediate/assemble-review.json | 11 + .../intermediate/assembled-graph.json | 1571 +++++++++++++++++ .../intermediate/batch-1.json | 634 +++++++ .../intermediate/batch-2.json | 943 ++++++++++ .../intermediate/batches.json | 149 ++ .../intermediate/import-input.json | 107 ++ .../intermediate/import-map.json | 27 + .../intermediate/scan-raw.json | 126 ++ .../intermediate/scan-result.json | 163 ++ .understand-anything/tmp/build-batches.cjs | 206 +++ .../tmp/ua-file-analyzer-input-1.json | 35 + .../tmp/ua-file-analyzer-input-2.json | 98 + .../tmp/ua-file-extract-results-1.json | 540 ++++++ .../tmp/ua-file-extract-results-2.json | 727 ++++++++ docs/goals/manual-first-auth-flow/GOAL.md | 13 + docs/goals/manual-first-auth-flow/PLAN.md | 440 +++++ tests/smoke.sh | 1 + 19 files changed, 5821 insertions(+) create mode 100644 .understand-anything/.understandignore create mode 100644 .understand-anything/config.json create mode 100644 .understand-anything/intermediate/assemble-review.json create mode 100644 .understand-anything/intermediate/assembled-graph.json create mode 100644 .understand-anything/intermediate/batch-1.json create mode 100644 .understand-anything/intermediate/batch-2.json create mode 100644 .understand-anything/intermediate/batches.json create mode 100644 .understand-anything/intermediate/import-input.json create mode 100644 .understand-anything/intermediate/import-map.json create mode 100644 .understand-anything/intermediate/scan-raw.json create mode 100644 .understand-anything/intermediate/scan-result.json create mode 100644 .understand-anything/tmp/build-batches.cjs create mode 100644 .understand-anything/tmp/ua-file-analyzer-input-1.json create mode 100644 .understand-anything/tmp/ua-file-analyzer-input-2.json create mode 100644 .understand-anything/tmp/ua-file-extract-results-1.json create mode 100644 .understand-anything/tmp/ua-file-extract-results-2.json create mode 100644 docs/goals/manual-first-auth-flow/GOAL.md create mode 100644 docs/goals/manual-first-auth-flow/PLAN.md diff --git a/.understand-anything/.understandignore b/.understand-anything/.understandignore new file mode 100644 index 0000000..795d9fb --- /dev/null +++ b/.understand-anything/.understandignore @@ -0,0 +1,27 @@ +# .understandignore - patterns for files/dirs to exclude from analysis +# Syntax: same as .gitignore (globs, # comments, ! negation, trailing / for dirs) +# Lines below are suggestions - uncomment to activate. +# Use ! prefix to force-include something excluded by defaults. +# +# Built-in defaults (always excluded unless negated): +# node_modules/, .git/, dist/, build/, obj/, *.lock, *.min.js, etc. +# +.understand-anything/ + +# --- From .gitignore (uncomment to exclude) --- + +# .DS_Store +# *.tmp +# .build/ +# app/.build/ + +# --- Detected directories (uncomment to exclude) --- + +# tests/ +# docs/ + +# --- Test file patterns (uncomment to exclude) --- + +# *.test.* +# *.spec.* +# *.snap diff --git a/.understand-anything/config.json b/.understand-anything/config.json new file mode 100644 index 0000000..58435d6 --- /dev/null +++ b/.understand-anything/config.json @@ -0,0 +1,3 @@ +{ + "outputLanguage": "ru" +} diff --git a/.understand-anything/intermediate/assemble-review.json b/.understand-anything/intermediate/assemble-review.json new file mode 100644 index 0000000..e5ee720 --- /dev/null +++ b/.understand-anything/intermediate/assemble-review.json @@ -0,0 +1,11 @@ +{ + "issues": [], + "warnings": [], + "stats": { + "nodes": 73, + "edges": 56, + "duplicates": 0, + "dangling": 0, + "missingFiles": 0 + } +} \ No newline at end of file diff --git a/.understand-anything/intermediate/assembled-graph.json b/.understand-anything/intermediate/assembled-graph.json new file mode 100644 index 0000000..0b642b5 --- /dev/null +++ b/.understand-anything/intermediate/assembled-graph.json @@ -0,0 +1,1571 @@ +{ + "nodes": [ + { + "id": "document:AGENTS.md", + "type": "document", + "name": "AGENTS.md", + "filePath": "AGENTS.md", + "summary": "Репозиторные правила для агентов: модель системы, live VPN safety, тестовая лестница и контракт финального ответа.", + "tags": [ + "agent-rules", + "documentation", + "safety" + ], + "complexity": "moderate" + }, + { + "id": "document:README.md", + "type": "document", + "name": "README.md", + "filePath": "README.md", + "summary": "Основная пользовательская документация Lemana VPN: установка, credential sources, модули, использование, диагностика и удаление.", + "tags": [ + "documentation", + "overview", + "user-guide" + ], + "complexity": "complex" + }, + { + "id": "file:install.sh", + "type": "file", + "name": "install.sh", + "filePath": "install.sh", + "summary": "Installer wizard для Homebrew/pipx/openconnect-lite, CLI, config, DNS wrapper, sudoers, Touch ID helper, Swift app, launch agent и shell aliases.", + "tags": [ + "installer", + "wizard", + "macos" + ], + "complexity": "complex" + }, + { + "id": "function:install.sh:usage", + "type": "function", + "name": "usage", + "filePath": "install.sh", + "lineRange": [ + 72, + 106 + ], + "summary": "Значимая функция usage в install.sh.", + "tags": [ + "function", + "runtime", + "usage" + ], + "complexity": "simple" + }, + { + "id": "function:install.sh:yes_no", + "type": "function", + "name": "yes_no", + "filePath": "install.sh", + "lineRange": [ + 304, + 330 + ], + "summary": "Значимая функция yes_no в install.sh.", + "tags": [ + "function", + "runtime", + "yes-no" + ], + "complexity": "simple" + }, + { + "id": "function:install.sh:choose_credential_source_interactive", + "type": "function", + "name": "choose_credential_source_interactive", + "filePath": "install.sh", + "lineRange": [ + 332, + 382 + ], + "summary": "Показывает интерактивный выбор credential source и нормализует ответ пользователя.", + "tags": [ + "function", + "runtime", + "choose-credential-source-interactive" + ], + "complexity": "moderate" + }, + { + "id": "function:install.sh:print_detected_state", + "type": "function", + "name": "print_detected_state", + "filePath": "install.sh", + "lineRange": [ + 400, + 417 + ], + "summary": "Значимая функция print_detected_state в install.sh.", + "tags": [ + "function", + "runtime", + "print-detected-state" + ], + "complexity": "simple" + }, + { + "id": "function:install.sh:choose_modules", + "type": "function", + "name": "choose_modules", + "filePath": "install.sh", + "lineRange": [ + 419, + 514 + ], + "summary": "Определяет набор устанавливаемых модулей с учетом интерактивного режима и флагов installer.", + "tags": [ + "function", + "runtime", + "choose-modules" + ], + "complexity": "moderate" + }, + { + "id": "function:install.sh:download_file", + "type": "function", + "name": "download_file", + "filePath": "install.sh", + "lineRange": [ + 523, + 540 + ], + "summary": "Значимая функция download_file в install.sh.", + "tags": [ + "function", + "runtime", + "download-file" + ], + "complexity": "simple" + }, + { + "id": "function:install.sh:install_homebrew_packages", + "type": "function", + "name": "install_homebrew_packages", + "filePath": "install.sh", + "lineRange": [ + 552, + 578 + ], + "summary": "Значимая функция install_homebrew_packages в install.sh.", + "tags": [ + "function", + "runtime", + "install-homebrew-packages" + ], + "complexity": "simple" + }, + { + "id": "function:install.sh:install_openconnect_lite", + "type": "function", + "name": "install_openconnect_lite", + "filePath": "install.sh", + "lineRange": [ + 580, + 597 + ], + "summary": "Устанавливает или проверяет openconnect-lite runtime, необходимый для SAML SSO.", + "tags": [ + "function", + "runtime", + "install-openconnect-lite" + ], + "complexity": "simple" + }, + { + "id": "function:install.sh:install_cli", + "type": "function", + "name": "install_cli", + "filePath": "install.sh", + "lineRange": [ + 599, + 613 + ], + "summary": "Копирует основной CLI и uninstall helper в пользовательский bin.", + "tags": [ + "function", + "runtime", + "install-cli" + ], + "complexity": "simple" + }, + { + "id": "function:install.sh:install_config", + "type": "function", + "name": "install_config", + "filePath": "install.sh", + "lineRange": [ + 615, + 639 + ], + "summary": "Устанавливает шаблон openconnect-lite config и сохраняет локальную конфигурацию модулей.", + "tags": [ + "function", + "runtime", + "install-config" + ], + "complexity": "simple" + }, + { + "id": "function:install.sh:install_dns_cleanup", + "type": "function", + "name": "install_dns_cleanup", + "filePath": "install.sh", + "lineRange": [ + 641, + 654 + ], + "summary": "Устанавливает root-owned DNS cleanup wrapper для безопасного сброса корпоративных DNS.", + "tags": [ + "function", + "runtime", + "install-dns-cleanup" + ], + "complexity": "simple" + }, + { + "id": "function:install.sh:install_sudoers", + "type": "function", + "name": "install_sudoers", + "filePath": "install.sh", + "lineRange": [ + 656, + 683 + ], + "summary": "Пишет ограниченные sudoers правила для openconnect и DNS cleanup wrapper.", + "tags": [ + "function", + "runtime", + "install-sudoers" + ], + "complexity": "simple" + }, + { + "id": "function:install.sh:install_touchid_helper", + "type": "function", + "name": "install_touchid_helper", + "filePath": "install.sh", + "lineRange": [ + 685, + 708 + ], + "summary": "Собирает и устанавливает Touch ID helper для Bitwarden master password.", + "tags": [ + "function", + "runtime", + "install-touchid-helper" + ], + "complexity": "simple" + }, + { + "id": "function:install.sh:install_menu_bar_app", + "type": "function", + "name": "install_menu_bar_app", + "filePath": "install.sh", + "lineRange": [ + 710, + 769 + ], + "summary": "Собирает release-версию Swift menu bar app и устанавливает LemanaVPN.app.", + "tags": [ + "function", + "runtime", + "install-menu-bar-app" + ], + "complexity": "moderate" + }, + { + "id": "function:install.sh:install_launch_agent", + "type": "function", + "name": "install_launch_agent", + "filePath": "install.sh", + "lineRange": [ + 771, + 811 + ], + "summary": "Создает LaunchAgent для автозапуска menu bar app при логине.", + "tags": [ + "function", + "runtime", + "install-launch-agent" + ], + "complexity": "simple" + }, + { + "id": "function:install.sh:restart_running_menu_bar_app", + "type": "function", + "name": "restart_running_menu_bar_app", + "filePath": "install.sh", + "lineRange": [ + 813, + 831 + ], + "summary": "Перезапускает уже открытое menu bar приложение после обновления bundle.", + "tags": [ + "function", + "runtime", + "restart-running-menu-bar-app" + ], + "complexity": "simple" + }, + { + "id": "function:install.sh:install_shell_aliases", + "type": "function", + "name": "install_shell_aliases", + "filePath": "install.sh", + "lineRange": [ + 833, + 877 + ], + "summary": "Добавляет idempotent zsh aliases для CLI режимов подключения и диагностики.", + "tags": [ + "function", + "runtime", + "install-shell-aliases" + ], + "complexity": "simple" + }, + { + "id": "function:install.sh:maybe_login_bitwarden", + "type": "function", + "name": "maybe_login_bitwarden", + "filePath": "install.sh", + "lineRange": [ + 879, + 900 + ], + "summary": "Проверяет Bitwarden session и при необходимости запускает login/unlock flow.", + "tags": [ + "function", + "runtime", + "maybe-login-bitwarden" + ], + "complexity": "simple" + }, + { + "id": "function:install.sh:main", + "type": "function", + "name": "main", + "filePath": "install.sh", + "lineRange": [ + 902, + 941 + ], + "summary": "Значимая функция main в install.sh.", + "tags": [ + "function", + "runtime", + "main" + ], + "complexity": "simple" + }, + { + "id": "file:uninstall.sh", + "type": "file", + "name": "uninstall.sh", + "filePath": "uninstall.sh", + "summary": "Uninstaller для восстановления runtime patch backups, удаления CLI/app/config/sudoers/aliases и опциональной очистки Keychain/Touch ID state.", + "tags": [ + "uninstaller", + "cleanup", + "macos" + ], + "complexity": "complex" + }, + { + "id": "function:uninstall.sh:usage", + "type": "function", + "name": "usage", + "filePath": "uninstall.sh", + "lineRange": [ + 62, + 79 + ], + "summary": "Значимая функция usage в uninstall.sh.", + "tags": [ + "function", + "runtime", + "usage" + ], + "complexity": "simple" + }, + { + "id": "function:uninstall.sh:restore_openconnect_lite_patch", + "type": "function", + "name": "restore_openconnect_lite_patch", + "filePath": "uninstall.sh", + "lineRange": [ + 161, + 181 + ], + "summary": "Возвращает backup исходника openconnect-lite, если runtime patch был установлен.", + "tags": [ + "function", + "runtime", + "restore-openconnect-lite-patch" + ], + "complexity": "simple" + }, + { + "id": "function:uninstall.sh:remove_zshrc_block", + "type": "function", + "name": "remove_zshrc_block", + "filePath": "uninstall.sh", + "lineRange": [ + 183, + 204 + ], + "summary": "Удаляет idempotent блок Lemana VPN aliases из zshrc.", + "tags": [ + "function", + "runtime", + "remove-zshrc-block" + ], + "complexity": "simple" + }, + { + "id": "function:uninstall.sh:remove_keychain_entries", + "type": "function", + "name": "remove_keychain_entries", + "filePath": "uninstall.sh", + "lineRange": [ + 206, + 234 + ], + "summary": "Опционально удаляет password, TOTP seed и username из macOS Keychain.", + "tags": [ + "function", + "runtime", + "remove-keychain-entries" + ], + "complexity": "simple" + }, + { + "id": "function:uninstall.sh:main", + "type": "function", + "name": "main", + "filePath": "uninstall.sh", + "lineRange": [ + 236, + 318 + ], + "summary": "Значимая функция main в uninstall.sh.", + "tags": [ + "function", + "runtime", + "main" + ], + "complexity": "moderate" + }, + { + "id": "document:.agents/skills/lemana-vpn-operations/SKILL.md", + "type": "document", + "name": "Lemana VPN Operations skill", + "filePath": ".agents/skills/lemana-vpn-operations/SKILL.md", + "summary": "Инструкции для операционных задач: установка, обновление, статусы, безопасные ответы пользователю и live-connection правила.", + "tags": [ + "skill", + "operations", + "documentation" + ], + "complexity": "moderate" + }, + { + "id": "document:.agents/skills/lemana-vpn-sso-autofill/SKILL.md", + "type": "document", + "name": "Lemana VPN SSO skill", + "filePath": ".agents/skills/lemana-vpn-sso-autofill/SKILL.md", + "summary": "Инструкции по Bitwarden, Keychain, TOTP, Keycloak SSO, autofill и runtime-патчам openconnect-lite.", + "tags": [ + "skill", + "sso", + "credentials" + ], + "complexity": "moderate" + }, + { + "id": "document:.agents/skills/lemana-vpn-testing/SKILL.md", + "type": "document", + "name": "Lemana VPN Testing skill", + "filePath": ".agents/skills/lemana-vpn-testing/SKILL.md", + "summary": "Инструкции по non-live проверкам, status checks, live VPN safety и финальному отчету для изменений в репозитории.", + "tags": [ + "skill", + "testing", + "verification" + ], + "complexity": "moderate" + }, + { + "id": "file:app/Package.swift", + "type": "file", + "name": "Package.swift", + "filePath": "app/Package.swift", + "summary": "Swift Package manifest для сборки executable target LemanaVPN под macOS 13+.", + "tags": [ + "swift", + "build-system", + "menu-bar-app" + ], + "complexity": "simple" + }, + { + "id": "file:app/Sources/LemanaVPN/LemanaVPNApp.swift", + "type": "file", + "name": "LemanaVPNApp.swift", + "filePath": "app/Sources/LemanaVPN/LemanaVPNApp.swift", + "summary": "SwiftUI/AppKit menu bar surface: отображает состояние VPN, кнопки подключения, логи и действия пользователя.", + "tags": [ + "swiftui", + "menu-bar", + "ui" + ], + "complexity": "moderate" + }, + { + "id": "class:app/Sources/LemanaVPN/LemanaVPNApp.swift:LemanaVPNApp", + "type": "class", + "name": "LemanaVPNApp", + "filePath": "app/Sources/LemanaVPN/LemanaVPNApp.swift", + "lineRange": [ + 5, + 16 + ], + "summary": "Точка входа SwiftUI menu bar app, связывает VPNManager с MenuBarExtra.", + "tags": [ + "swift", + "type", + "menu-bar-app" + ], + "complexity": "simple" + }, + { + "id": "class:app/Sources/LemanaVPN/LemanaVPNApp.swift:StatusBarLabel", + "type": "class", + "name": "StatusBarLabel", + "filePath": "app/Sources/LemanaVPN/LemanaVPNApp.swift", + "lineRange": [ + 17, + 51 + ], + "summary": "Компактный status bar label с иконкой и оставшимся временем сессии.", + "tags": [ + "swift", + "type", + "menu-bar-app" + ], + "complexity": "simple" + }, + { + "id": "class:app/Sources/LemanaVPN/LemanaVPNApp.swift:VPNMenuView", + "type": "class", + "name": "VPNMenuView", + "filePath": "app/Sources/LemanaVPN/LemanaVPNApp.swift", + "lineRange": [ + 52, + 191 + ], + "summary": "Основной menu UI с командами auto/manual/manual-full connect, disconnect, logs и quit.", + "tags": [ + "swift", + "type", + "menu-bar-app" + ], + "complexity": "moderate" + }, + { + "id": "file:app/Sources/LemanaVPN/VPNManager.swift", + "type": "file", + "name": "VPNManager.swift", + "filePath": "app/Sources/LemanaVPN/VPNManager.swift", + "summary": "Основной state manager Swift-приложения: запускает CLI в JSON mode, декодирует события, отслеживает health и управляет reconnect/disconnect.", + "tags": [ + "swift", + "state-manager", + "cli-bridge" + ], + "complexity": "complex" + }, + { + "id": "class:app/Sources/LemanaVPN/VPNManager.swift:VPNEvent", + "type": "class", + "name": "VPNEvent", + "filePath": "app/Sources/LemanaVPN/VPNManager.swift", + "lineRange": [ + 4, + 17 + ], + "summary": "Decodable-модель JSON events, которые runtime CLI отправляет в Swift app.", + "tags": [ + "swift", + "type", + "menu-bar-app" + ], + "complexity": "simple" + }, + { + "id": "class:app/Sources/LemanaVPN/VPNManager.swift:VPNStatusResponse", + "type": "class", + "name": "VPNStatusResponse", + "filePath": "app/Sources/LemanaVPN/VPNManager.swift", + "lineRange": [ + 18, + 21 + ], + "summary": "Decodable envelope для ответа `vpn --status --json`.", + "tags": [ + "swift", + "type", + "menu-bar-app" + ], + "complexity": "simple" + }, + { + "id": "class:app/Sources/LemanaVPN/VPNManager.swift:ModuleStatus", + "type": "class", + "name": "ModuleStatus", + "filePath": "app/Sources/LemanaVPN/VPNManager.swift", + "lineRange": [ + 22, + 101 + ], + "summary": "Модель статуса модулей и вычисление summary/systemImage для menu bar app.", + "tags": [ + "swift", + "type", + "menu-bar-app" + ], + "complexity": "moderate" + }, + { + "id": "class:app/Sources/LemanaVPN/VPNManager.swift:VPNState", + "type": "class", + "name": "VPNState", + "filePath": "app/Sources/LemanaVPN/VPNManager.swift", + "lineRange": [ + 102, + 121 + ], + "summary": "Finite state model для disconnected/connecting/connected/reconnecting/error состояний.", + "tags": [ + "swift", + "type", + "menu-bar-app" + ], + "complexity": "simple" + }, + { + "id": "class:app/Sources/LemanaVPN/VPNManager.swift:VPNLaunchMode", + "type": "class", + "name": "VPNLaunchMode", + "filePath": "app/Sources/LemanaVPN/VPNManager.swift", + "lineRange": [ + 122, + 136 + ], + "summary": "Маппинг app launch modes на CLI flags `--auto`, `--manual`, `--manual-full`.", + "tags": [ + "swift", + "type", + "menu-bar-app" + ], + "complexity": "simple" + }, + { + "id": "class:app/Sources/LemanaVPN/VPNManager.swift:VPNManager", + "type": "class", + "name": "VPNManager", + "filePath": "app/Sources/LemanaVPN/VPNManager.swift", + "lineRange": [ + 137, + 698 + ], + "summary": "ObservableObject, который мостит Swift menu bar app с shell CLI, JSON events и health checks.", + "tags": [ + "swift", + "type", + "state-manager" + ], + "complexity": "complex" + }, + { + "id": "function:app/Sources/LemanaVPN/VPNManager.swift:refreshStatus", + "type": "function", + "name": "refreshStatus", + "filePath": "app/Sources/LemanaVPN/VPNManager.swift", + "lineRange": [ + 194, + 248 + ], + "summary": "Запускает CLI status JSON и обновляет module summary в menu bar app.", + "tags": [ + "function", + "runtime", + "refreshStatus" + ], + "complexity": "moderate" + }, + { + "id": "function:app/Sources/LemanaVPN/VPNManager.swift:connect", + "type": "function", + "name": "connect", + "filePath": "app/Sources/LemanaVPN/VPNManager.swift", + "lineRange": [ + 249, + 326 + ], + "summary": "Запускает CLI в JSON mode с выбранным режимом подключения и подписывается на stdout/stderr.", + "tags": [ + "function", + "runtime", + "connect" + ], + "complexity": "moderate" + }, + { + "id": "function:app/Sources/LemanaVPN/VPNManager.swift:disconnect", + "type": "function", + "name": "disconnect", + "filePath": "app/Sources/LemanaVPN/VPNManager.swift", + "lineRange": [ + 327, + 349 + ], + "summary": "Отправляет SIGINT текущему CLI process и останавливает reconnect state.", + "tags": [ + "function", + "runtime", + "disconnect" + ], + "complexity": "simple" + }, + { + "id": "function:app/Sources/LemanaVPN/VPNManager.swift:parseEvent", + "type": "function", + "name": "parseEvent", + "filePath": "app/Sources/LemanaVPN/VPNManager.swift", + "lineRange": [ + 350, + 448 + ], + "summary": "Декодирует JSON events из CLI и переводит их в Swift state updates.", + "tags": [ + "function", + "runtime", + "parseEvent" + ], + "complexity": "moderate" + }, + { + "id": "function:app/Sources/LemanaVPN/VPNManager.swift:handleTermination", + "type": "function", + "name": "handleTermination", + "filePath": "app/Sources/LemanaVPN/VPNManager.swift", + "lineRange": [ + 449, + 576 + ], + "summary": "Обрабатывает завершение CLI process и решает, нужен ли автоматический reconnect.", + "tags": [ + "function", + "runtime", + "handleTermination" + ], + "complexity": "moderate" + }, + { + "id": "function:app/Sources/LemanaVPN/VPNManager.swift:startHealthCheck", + "type": "function", + "name": "startHealthCheck", + "filePath": "app/Sources/LemanaVPN/VPNManager.swift", + "lineRange": [ + 577, + 666 + ], + "summary": "Периодически проверяет tunnel health и запускает reconnect при повторных failures.", + "tags": [ + "function", + "runtime", + "startHealthCheck" + ], + "complexity": "moderate" + }, + { + "id": "file:bin/vpn-lemanapro.sh", + "type": "file", + "name": "vpn-lemanapro.sh", + "filePath": "bin/vpn-lemanapro.sh", + "summary": "Главный runtime CLI: status JSON, sync credentials, Keychain/TOTP, runtime patches, SSO запуск, reconnect loop, DNS cleanup и фильтрация openconnect-lite output.", + "tags": [ + "cli", + "runtime", + "sso" + ], + "complexity": "complex" + }, + { + "id": "function:bin/vpn-lemanapro.sh:_module_bool", + "type": "function", + "name": "_module_bool", + "filePath": "bin/vpn-lemanapro.sh", + "lineRange": [ + 197, + 203 + ], + "summary": "Значимая функция _module_bool в bin/vpn-lemanapro.sh.", + "tags": [ + "function", + "runtime", + "module-bool" + ], + "complexity": "simple" + }, + { + "id": "function:bin/vpn-lemanapro.sh:_patches_active", + "type": "function", + "name": "_patches_active", + "filePath": "bin/vpn-lemanapro.sh", + "lineRange": [ + 205, + 219 + ], + "summary": "Значимая функция _patches_active в bin/vpn-lemanapro.sh.", + "tags": [ + "function", + "runtime", + "patches-active" + ], + "complexity": "simple" + }, + { + "id": "function:bin/vpn-lemanapro.sh:_module_status_json", + "type": "function", + "name": "_module_status_json", + "filePath": "bin/vpn-lemanapro.sh", + "lineRange": [ + 225, + 264 + ], + "summary": "Собирает машинно-читаемый JSON со статусом модулей, credentials, app, DNS и patches.", + "tags": [ + "function", + "runtime", + "module-status-json" + ], + "complexity": "simple" + }, + { + "id": "function:bin/vpn-lemanapro.sh:_module_human_part", + "type": "function", + "name": "_module_human_part", + "filePath": "bin/vpn-lemanapro.sh", + "lineRange": [ + 266, + 275 + ], + "summary": "Значимая функция _module_human_part в bin/vpn-lemanapro.sh.", + "tags": [ + "function", + "runtime", + "module-human-part" + ], + "complexity": "simple" + }, + { + "id": "function:bin/vpn-lemanapro.sh:_module_status_human", + "type": "function", + "name": "_module_status_human", + "filePath": "bin/vpn-lemanapro.sh", + "lineRange": [ + 277, + 311 + ], + "summary": "Печатает человекочитаемую строку статуса модулей перед подключением.", + "tags": [ + "function", + "runtime", + "module-status-human" + ], + "complexity": "simple" + }, + { + "id": "function:bin/vpn-lemanapro.sh:_check_status", + "type": "function", + "name": "_check_status", + "filePath": "bin/vpn-lemanapro.sh", + "lineRange": [ + 313, + 375 + ], + "summary": "Проверяет PID/status file, running process, DNS target и возвращает disconnected/connected JSON.", + "tags": [ + "function", + "runtime", + "check-status" + ], + "complexity": "moderate" + }, + { + "id": "function:bin/vpn-lemanapro.sh:_patch_oc", + "type": "function", + "name": "_patch_oc", + "filePath": "bin/vpn-lemanapro.sh", + "lineRange": [ + 377, + 604 + ], + "summary": "Применяет runtime patches к openconnect-lite для Keycloak autofill/manual/debug сценариев.", + "tags": [ + "function", + "runtime", + "patch-oc" + ], + "complexity": "complex" + }, + { + "id": "function:bin/vpn-lemanapro.sh:_normalize_totp_secret", + "type": "function", + "name": "_normalize_totp_secret", + "filePath": "bin/vpn-lemanapro.sh", + "lineRange": [ + 623, + 649 + ], + "summary": "Нормализует TOTP secret из raw secret или otpauth URI перед сохранением.", + "tags": [ + "function", + "runtime", + "normalize-totp-secret" + ], + "complexity": "simple" + }, + { + "id": "function:bin/vpn-lemanapro.sh:_persist_username", + "type": "function", + "name": "_persist_username", + "filePath": "bin/vpn-lemanapro.sh", + "lineRange": [ + 675, + 718 + ], + "summary": "Значимая функция _persist_username в bin/vpn-lemanapro.sh.", + "tags": [ + "function", + "runtime", + "persist-username" + ], + "complexity": "simple" + }, + { + "id": "function:bin/vpn-lemanapro.sh:_ensure_username", + "type": "function", + "name": "_ensure_username", + "filePath": "bin/vpn-lemanapro.sh", + "lineRange": [ + 720, + 745 + ], + "summary": "Значимая функция _ensure_username в bin/vpn-lemanapro.sh.", + "tags": [ + "function", + "runtime", + "ensure-username" + ], + "complexity": "simple" + }, + { + "id": "function:bin/vpn-lemanapro.sh:_configure_keychain", + "type": "function", + "name": "_configure_keychain", + "filePath": "bin/vpn-lemanapro.sh", + "lineRange": [ + 747, + 785 + ], + "summary": "Интерактивно сохраняет LDAP password и TOTP seed в macOS Keychain.", + "tags": [ + "function", + "runtime", + "configure-keychain" + ], + "complexity": "simple" + }, + { + "id": "function:bin/vpn-lemanapro.sh:_ensure_keychain_credentials", + "type": "function", + "name": "_ensure_keychain_credentials", + "filePath": "bin/vpn-lemanapro.sh", + "lineRange": [ + 787, + 814 + ], + "summary": "Проверяет наличие обязательных Keychain credentials перед auto/manual подключением.", + "tags": [ + "function", + "runtime", + "ensure-keychain-credentials" + ], + "complexity": "simple" + }, + { + "id": "function:bin/vpn-lemanapro.sh:_bw_unlock", + "type": "function", + "name": "_bw_unlock", + "filePath": "bin/vpn-lemanapro.sh", + "lineRange": [ + 823, + 889 + ], + "summary": "Открывает Bitwarden session с учетом Touch ID helper и fallback prompts.", + "tags": [ + "function", + "runtime", + "bw-unlock" + ], + "complexity": "moderate" + }, + { + "id": "function:bin/vpn-lemanapro.sh:_sync_bitwarden", + "type": "function", + "name": "_sync_bitwarden", + "filePath": "bin/vpn-lemanapro.sh", + "lineRange": [ + 891, + 925 + ], + "summary": "Синхронизирует LDAP password и TOTP seed из Bitwarden в macOS Keychain.", + "tags": [ + "function", + "runtime", + "sync-bitwarden" + ], + "complexity": "simple" + }, + { + "id": "function:bin/vpn-lemanapro.sh:_sync_credentials", + "type": "function", + "name": "_sync_credentials", + "filePath": "bin/vpn-lemanapro.sh", + "lineRange": [ + 927, + 936 + ], + "summary": "Выбирает credential source и запускает соответствующий sync/preflight путь.", + "tags": [ + "function", + "runtime", + "sync-credentials" + ], + "complexity": "simple" + }, + { + "id": "function:bin/vpn-lemanapro.sh:_dns_cleanup", + "type": "function", + "name": "_dns_cleanup", + "filePath": "bin/vpn-lemanapro.sh", + "lineRange": [ + 938, + 954 + ], + "summary": "Вызывает narrow sudo wrapper для очистки VPN DNS при завершении сессии.", + "tags": [ + "function", + "runtime", + "dns-cleanup" + ], + "complexity": "simple" + }, + { + "id": "function:bin/vpn-lemanapro.sh:_filter_output", + "type": "function", + "name": "_filter_output", + "filePath": "bin/vpn-lemanapro.sh", + "lineRange": [ + 956, + 1006 + ], + "summary": "Фильтрует поток openconnect-lite, извлекает IP/expiry, пишет status JSON и события для app.", + "tags": [ + "function", + "runtime", + "filter-output" + ], + "complexity": "moderate" + }, + { + "id": "document:docs/goals/manual-first-auth-flow/GOAL.md", + "type": "document", + "name": "Manual-first auth goal", + "filePath": "docs/goals/manual-first-auth-flow/GOAL.md", + "summary": "Краткое описание цели manual-first authentication flow и ожидаемого пользовательского результата.", + "tags": [ + "goal", + "auth-flow", + "planning" + ], + "complexity": "simple" + }, + { + "id": "document:docs/goals/manual-first-auth-flow/PLAN.md", + "type": "document", + "name": "Manual-first auth plan", + "filePath": "docs/goals/manual-first-auth-flow/PLAN.md", + "summary": "Подробный план изменения installer UX, CLI auth profiles, Swift menu-bar surface и smoke coverage для manual-first сценария.", + "tags": [ + "plan", + "auth-flow", + "implementation" + ], + "complexity": "complex" + }, + { + "id": "document:docs/superpowers/plans/2026-05-24-keychain-totp-provider.md", + "type": "document", + "name": "Keychain TOTP provider plan", + "filePath": "docs/superpowers/plans/2026-05-24-keychain-totp-provider.md", + "summary": "Исторический implementation plan для разделения credential sources, нормализации TOTP и расширения smoke-проверок.", + "tags": [ + "plan", + "keychain", + "totp" + ], + "complexity": "complex" + }, + { + "id": "file:libexec/lemana-vpn-dns-cleanup", + "type": "file", + "name": "lemana-vpn-dns-cleanup", + "filePath": "libexec/lemana-vpn-dns-cleanup", + "summary": "Узкий root-owned wrapper, который сбрасывает только корпоративные DNS и обновляет macOS resolver cache.", + "tags": [ + "dns", + "sudo-wrapper", + "cleanup" + ], + "complexity": "simple" + }, + { + "id": "config:templates/openconnect-lite-config.toml", + "type": "config", + "name": "openconnect-lite-config.toml", + "filePath": "templates/openconnect-lite-config.toml", + "summary": "Шаблон профиля openconnect-lite: gateway, credentials mode и Keycloak auto-fill selectors для username, password и TOTP.", + "tags": [ + "configuration", + "openconnect-lite", + "autofill" + ], + "complexity": "simple" + }, + { + "id": "file:tests/smoke.sh", + "type": "file", + "name": "smoke.sh", + "filePath": "tests/smoke.sh", + "summary": "Non-live smoke suite: shell syntax, installer/uninstaller checks, Swift wiring, SSO patch snippets, config selectors и regression assertions.", + "tags": [ + "test", + "smoke", + "verification" + ], + "complexity": "complex" + } + ], + "edges": [ + { + "source": "file:install.sh", + "target": "function:install.sh:usage", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:install.sh", + "target": "function:install.sh:yes_no", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:install.sh", + "target": "function:install.sh:choose_credential_source_interactive", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:install.sh", + "target": "function:install.sh:print_detected_state", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:install.sh", + "target": "function:install.sh:choose_modules", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:install.sh", + "target": "function:install.sh:download_file", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:install.sh", + "target": "function:install.sh:install_homebrew_packages", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:install.sh", + "target": "function:install.sh:install_openconnect_lite", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:install.sh", + "target": "function:install.sh:install_cli", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:install.sh", + "target": "function:install.sh:install_config", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:install.sh", + "target": "function:install.sh:install_dns_cleanup", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:install.sh", + "target": "function:install.sh:install_sudoers", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:install.sh", + "target": "function:install.sh:install_touchid_helper", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:install.sh", + "target": "function:install.sh:install_menu_bar_app", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:install.sh", + "target": "function:install.sh:install_launch_agent", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:install.sh", + "target": "function:install.sh:restart_running_menu_bar_app", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:install.sh", + "target": "function:install.sh:install_shell_aliases", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:install.sh", + "target": "function:install.sh:maybe_login_bitwarden", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:install.sh", + "target": "function:install.sh:main", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:uninstall.sh", + "target": "function:uninstall.sh:usage", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:uninstall.sh", + "target": "function:uninstall.sh:restore_openconnect_lite_patch", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:uninstall.sh", + "target": "function:uninstall.sh:remove_zshrc_block", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:uninstall.sh", + "target": "function:uninstall.sh:remove_keychain_entries", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:uninstall.sh", + "target": "function:uninstall.sh:main", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:app/Sources/LemanaVPN/LemanaVPNApp.swift", + "target": "class:app/Sources/LemanaVPN/LemanaVPNApp.swift:LemanaVPNApp", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:app/Sources/LemanaVPN/LemanaVPNApp.swift", + "target": "class:app/Sources/LemanaVPN/LemanaVPNApp.swift:StatusBarLabel", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:app/Sources/LemanaVPN/LemanaVPNApp.swift", + "target": "class:app/Sources/LemanaVPN/LemanaVPNApp.swift:VPNMenuView", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:app/Sources/LemanaVPN/VPNManager.swift", + "target": "class:app/Sources/LemanaVPN/VPNManager.swift:VPNEvent", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:app/Sources/LemanaVPN/VPNManager.swift", + "target": "class:app/Sources/LemanaVPN/VPNManager.swift:VPNStatusResponse", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:app/Sources/LemanaVPN/VPNManager.swift", + "target": "class:app/Sources/LemanaVPN/VPNManager.swift:ModuleStatus", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:app/Sources/LemanaVPN/VPNManager.swift", + "target": "class:app/Sources/LemanaVPN/VPNManager.swift:VPNState", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:app/Sources/LemanaVPN/VPNManager.swift", + "target": "class:app/Sources/LemanaVPN/VPNManager.swift:VPNLaunchMode", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:app/Sources/LemanaVPN/VPNManager.swift", + "target": "class:app/Sources/LemanaVPN/VPNManager.swift:VPNManager", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:app/Sources/LemanaVPN/VPNManager.swift", + "target": "function:app/Sources/LemanaVPN/VPNManager.swift:refreshStatus", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:app/Sources/LemanaVPN/VPNManager.swift", + "target": "function:app/Sources/LemanaVPN/VPNManager.swift:connect", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:app/Sources/LemanaVPN/VPNManager.swift", + "target": "function:app/Sources/LemanaVPN/VPNManager.swift:disconnect", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:app/Sources/LemanaVPN/VPNManager.swift", + "target": "function:app/Sources/LemanaVPN/VPNManager.swift:parseEvent", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:app/Sources/LemanaVPN/VPNManager.swift", + "target": "function:app/Sources/LemanaVPN/VPNManager.swift:handleTermination", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:app/Sources/LemanaVPN/VPNManager.swift", + "target": "function:app/Sources/LemanaVPN/VPNManager.swift:startHealthCheck", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:bin/vpn-lemanapro.sh", + "target": "function:bin/vpn-lemanapro.sh:_module_bool", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:bin/vpn-lemanapro.sh", + "target": "function:bin/vpn-lemanapro.sh:_patches_active", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:bin/vpn-lemanapro.sh", + "target": "function:bin/vpn-lemanapro.sh:_module_status_json", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:bin/vpn-lemanapro.sh", + "target": "function:bin/vpn-lemanapro.sh:_module_human_part", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:bin/vpn-lemanapro.sh", + "target": "function:bin/vpn-lemanapro.sh:_module_status_human", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:bin/vpn-lemanapro.sh", + "target": "function:bin/vpn-lemanapro.sh:_check_status", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:bin/vpn-lemanapro.sh", + "target": "function:bin/vpn-lemanapro.sh:_patch_oc", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:bin/vpn-lemanapro.sh", + "target": "function:bin/vpn-lemanapro.sh:_normalize_totp_secret", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:bin/vpn-lemanapro.sh", + "target": "function:bin/vpn-lemanapro.sh:_persist_username", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:bin/vpn-lemanapro.sh", + "target": "function:bin/vpn-lemanapro.sh:_ensure_username", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:bin/vpn-lemanapro.sh", + "target": "function:bin/vpn-lemanapro.sh:_configure_keychain", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:bin/vpn-lemanapro.sh", + "target": "function:bin/vpn-lemanapro.sh:_ensure_keychain_credentials", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:bin/vpn-lemanapro.sh", + "target": "function:bin/vpn-lemanapro.sh:_bw_unlock", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:bin/vpn-lemanapro.sh", + "target": "function:bin/vpn-lemanapro.sh:_sync_bitwarden", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:bin/vpn-lemanapro.sh", + "target": "function:bin/vpn-lemanapro.sh:_sync_credentials", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:bin/vpn-lemanapro.sh", + "target": "function:bin/vpn-lemanapro.sh:_dns_cleanup", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:bin/vpn-lemanapro.sh", + "target": "function:bin/vpn-lemanapro.sh:_filter_output", + "type": "contains", + "direction": "forward", + "weight": 1 + } + ] +} \ No newline at end of file diff --git a/.understand-anything/intermediate/batch-1.json b/.understand-anything/intermediate/batch-1.json new file mode 100644 index 0000000..635feaf --- /dev/null +++ b/.understand-anything/intermediate/batch-1.json @@ -0,0 +1,634 @@ +{ + "nodes": [ + { + "id": "document:AGENTS.md", + "type": "document", + "name": "AGENTS.md", + "filePath": "AGENTS.md", + "summary": "Репозиторные правила для агентов: модель системы, live VPN safety, тестовая лестница и контракт финального ответа.", + "tags": [ + "agent-rules", + "documentation", + "safety" + ], + "complexity": "moderate" + }, + { + "id": "document:README.md", + "type": "document", + "name": "README.md", + "filePath": "README.md", + "summary": "Основная пользовательская документация Lemana VPN: установка, credential sources, модули, использование, диагностика и удаление.", + "tags": [ + "documentation", + "overview", + "user-guide" + ], + "complexity": "complex" + }, + { + "id": "file:install.sh", + "type": "file", + "name": "install.sh", + "filePath": "install.sh", + "summary": "Installer wizard для Homebrew/pipx/openconnect-lite, CLI, config, DNS wrapper, sudoers, Touch ID helper, Swift app, launch agent и shell aliases.", + "tags": [ + "installer", + "wizard", + "macos" + ], + "complexity": "complex" + }, + { + "id": "function:install.sh:usage", + "type": "function", + "name": "usage", + "filePath": "install.sh", + "lineRange": [ + 72, + 106 + ], + "summary": "Значимая функция usage в install.sh.", + "tags": [ + "function", + "runtime", + "usage" + ], + "complexity": "simple" + }, + { + "id": "function:install.sh:yes_no", + "type": "function", + "name": "yes_no", + "filePath": "install.sh", + "lineRange": [ + 304, + 330 + ], + "summary": "Значимая функция yes_no в install.sh.", + "tags": [ + "function", + "runtime", + "yes-no" + ], + "complexity": "simple" + }, + { + "id": "function:install.sh:choose_credential_source_interactive", + "type": "function", + "name": "choose_credential_source_interactive", + "filePath": "install.sh", + "lineRange": [ + 332, + 382 + ], + "summary": "Показывает интерактивный выбор credential source и нормализует ответ пользователя.", + "tags": [ + "function", + "runtime", + "choose-credential-source-interactive" + ], + "complexity": "moderate" + }, + { + "id": "function:install.sh:print_detected_state", + "type": "function", + "name": "print_detected_state", + "filePath": "install.sh", + "lineRange": [ + 400, + 417 + ], + "summary": "Значимая функция print_detected_state в install.sh.", + "tags": [ + "function", + "runtime", + "print-detected-state" + ], + "complexity": "simple" + }, + { + "id": "function:install.sh:choose_modules", + "type": "function", + "name": "choose_modules", + "filePath": "install.sh", + "lineRange": [ + 419, + 514 + ], + "summary": "Определяет набор устанавливаемых модулей с учетом интерактивного режима и флагов installer.", + "tags": [ + "function", + "runtime", + "choose-modules" + ], + "complexity": "moderate" + }, + { + "id": "function:install.sh:download_file", + "type": "function", + "name": "download_file", + "filePath": "install.sh", + "lineRange": [ + 523, + 540 + ], + "summary": "Значимая функция download_file в install.sh.", + "tags": [ + "function", + "runtime", + "download-file" + ], + "complexity": "simple" + }, + { + "id": "function:install.sh:install_homebrew_packages", + "type": "function", + "name": "install_homebrew_packages", + "filePath": "install.sh", + "lineRange": [ + 552, + 578 + ], + "summary": "Значимая функция install_homebrew_packages в install.sh.", + "tags": [ + "function", + "runtime", + "install-homebrew-packages" + ], + "complexity": "simple" + }, + { + "id": "function:install.sh:install_openconnect_lite", + "type": "function", + "name": "install_openconnect_lite", + "filePath": "install.sh", + "lineRange": [ + 580, + 597 + ], + "summary": "Устанавливает или проверяет openconnect-lite runtime, необходимый для SAML SSO.", + "tags": [ + "function", + "runtime", + "install-openconnect-lite" + ], + "complexity": "simple" + }, + { + "id": "function:install.sh:install_cli", + "type": "function", + "name": "install_cli", + "filePath": "install.sh", + "lineRange": [ + 599, + 613 + ], + "summary": "Копирует основной CLI и uninstall helper в пользовательский bin.", + "tags": [ + "function", + "runtime", + "install-cli" + ], + "complexity": "simple" + }, + { + "id": "function:install.sh:install_config", + "type": "function", + "name": "install_config", + "filePath": "install.sh", + "lineRange": [ + 615, + 639 + ], + "summary": "Устанавливает шаблон openconnect-lite config и сохраняет локальную конфигурацию модулей.", + "tags": [ + "function", + "runtime", + "install-config" + ], + "complexity": "simple" + }, + { + "id": "function:install.sh:install_dns_cleanup", + "type": "function", + "name": "install_dns_cleanup", + "filePath": "install.sh", + "lineRange": [ + 641, + 654 + ], + "summary": "Устанавливает root-owned DNS cleanup wrapper для безопасного сброса корпоративных DNS.", + "tags": [ + "function", + "runtime", + "install-dns-cleanup" + ], + "complexity": "simple" + }, + { + "id": "function:install.sh:install_sudoers", + "type": "function", + "name": "install_sudoers", + "filePath": "install.sh", + "lineRange": [ + 656, + 683 + ], + "summary": "Пишет ограниченные sudoers правила для openconnect и DNS cleanup wrapper.", + "tags": [ + "function", + "runtime", + "install-sudoers" + ], + "complexity": "simple" + }, + { + "id": "function:install.sh:install_touchid_helper", + "type": "function", + "name": "install_touchid_helper", + "filePath": "install.sh", + "lineRange": [ + 685, + 708 + ], + "summary": "Собирает и устанавливает Touch ID helper для Bitwarden master password.", + "tags": [ + "function", + "runtime", + "install-touchid-helper" + ], + "complexity": "simple" + }, + { + "id": "function:install.sh:install_menu_bar_app", + "type": "function", + "name": "install_menu_bar_app", + "filePath": "install.sh", + "lineRange": [ + 710, + 769 + ], + "summary": "Собирает release-версию Swift menu bar app и устанавливает LemanaVPN.app.", + "tags": [ + "function", + "runtime", + "install-menu-bar-app" + ], + "complexity": "moderate" + }, + { + "id": "function:install.sh:install_launch_agent", + "type": "function", + "name": "install_launch_agent", + "filePath": "install.sh", + "lineRange": [ + 771, + 811 + ], + "summary": "Создает LaunchAgent для автозапуска menu bar app при логине.", + "tags": [ + "function", + "runtime", + "install-launch-agent" + ], + "complexity": "simple" + }, + { + "id": "function:install.sh:restart_running_menu_bar_app", + "type": "function", + "name": "restart_running_menu_bar_app", + "filePath": "install.sh", + "lineRange": [ + 813, + 831 + ], + "summary": "Перезапускает уже открытое menu bar приложение после обновления bundle.", + "tags": [ + "function", + "runtime", + "restart-running-menu-bar-app" + ], + "complexity": "simple" + }, + { + "id": "function:install.sh:install_shell_aliases", + "type": "function", + "name": "install_shell_aliases", + "filePath": "install.sh", + "lineRange": [ + 833, + 877 + ], + "summary": "Добавляет idempotent zsh aliases для CLI режимов подключения и диагностики.", + "tags": [ + "function", + "runtime", + "install-shell-aliases" + ], + "complexity": "simple" + }, + { + "id": "function:install.sh:maybe_login_bitwarden", + "type": "function", + "name": "maybe_login_bitwarden", + "filePath": "install.sh", + "lineRange": [ + 879, + 900 + ], + "summary": "Проверяет Bitwarden session и при необходимости запускает login/unlock flow.", + "tags": [ + "function", + "runtime", + "maybe-login-bitwarden" + ], + "complexity": "simple" + }, + { + "id": "function:install.sh:main", + "type": "function", + "name": "main", + "filePath": "install.sh", + "lineRange": [ + 902, + 941 + ], + "summary": "Значимая функция main в install.sh.", + "tags": [ + "function", + "runtime", + "main" + ], + "complexity": "simple" + }, + { + "id": "file:uninstall.sh", + "type": "file", + "name": "uninstall.sh", + "filePath": "uninstall.sh", + "summary": "Uninstaller для восстановления runtime patch backups, удаления CLI/app/config/sudoers/aliases и опциональной очистки Keychain/Touch ID state.", + "tags": [ + "uninstaller", + "cleanup", + "macos" + ], + "complexity": "complex" + }, + { + "id": "function:uninstall.sh:usage", + "type": "function", + "name": "usage", + "filePath": "uninstall.sh", + "lineRange": [ + 62, + 79 + ], + "summary": "Значимая функция usage в uninstall.sh.", + "tags": [ + "function", + "runtime", + "usage" + ], + "complexity": "simple" + }, + { + "id": "function:uninstall.sh:restore_openconnect_lite_patch", + "type": "function", + "name": "restore_openconnect_lite_patch", + "filePath": "uninstall.sh", + "lineRange": [ + 161, + 181 + ], + "summary": "Возвращает backup исходника openconnect-lite, если runtime patch был установлен.", + "tags": [ + "function", + "runtime", + "restore-openconnect-lite-patch" + ], + "complexity": "simple" + }, + { + "id": "function:uninstall.sh:remove_zshrc_block", + "type": "function", + "name": "remove_zshrc_block", + "filePath": "uninstall.sh", + "lineRange": [ + 183, + 204 + ], + "summary": "Удаляет idempotent блок Lemana VPN aliases из zshrc.", + "tags": [ + "function", + "runtime", + "remove-zshrc-block" + ], + "complexity": "simple" + }, + { + "id": "function:uninstall.sh:remove_keychain_entries", + "type": "function", + "name": "remove_keychain_entries", + "filePath": "uninstall.sh", + "lineRange": [ + 206, + 234 + ], + "summary": "Опционально удаляет password, TOTP seed и username из macOS Keychain.", + "tags": [ + "function", + "runtime", + "remove-keychain-entries" + ], + "complexity": "simple" + }, + { + "id": "function:uninstall.sh:main", + "type": "function", + "name": "main", + "filePath": "uninstall.sh", + "lineRange": [ + 236, + 318 + ], + "summary": "Значимая функция main в uninstall.sh.", + "tags": [ + "function", + "runtime", + "main" + ], + "complexity": "moderate" + } + ], + "edges": [ + { + "source": "file:install.sh", + "target": "function:install.sh:usage", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:install.sh", + "target": "function:install.sh:yes_no", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:install.sh", + "target": "function:install.sh:choose_credential_source_interactive", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:install.sh", + "target": "function:install.sh:print_detected_state", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:install.sh", + "target": "function:install.sh:choose_modules", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:install.sh", + "target": "function:install.sh:download_file", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:install.sh", + "target": "function:install.sh:install_homebrew_packages", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:install.sh", + "target": "function:install.sh:install_openconnect_lite", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:install.sh", + "target": "function:install.sh:install_cli", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:install.sh", + "target": "function:install.sh:install_config", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:install.sh", + "target": "function:install.sh:install_dns_cleanup", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:install.sh", + "target": "function:install.sh:install_sudoers", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:install.sh", + "target": "function:install.sh:install_touchid_helper", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:install.sh", + "target": "function:install.sh:install_menu_bar_app", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:install.sh", + "target": "function:install.sh:install_launch_agent", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:install.sh", + "target": "function:install.sh:restart_running_menu_bar_app", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:install.sh", + "target": "function:install.sh:install_shell_aliases", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:install.sh", + "target": "function:install.sh:maybe_login_bitwarden", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:install.sh", + "target": "function:install.sh:main", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:uninstall.sh", + "target": "function:uninstall.sh:usage", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:uninstall.sh", + "target": "function:uninstall.sh:restore_openconnect_lite_patch", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:uninstall.sh", + "target": "function:uninstall.sh:remove_zshrc_block", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:uninstall.sh", + "target": "function:uninstall.sh:remove_keychain_entries", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:uninstall.sh", + "target": "function:uninstall.sh:main", + "type": "contains", + "direction": "forward", + "weight": 1 + } + ] +} \ No newline at end of file diff --git a/.understand-anything/intermediate/batch-2.json b/.understand-anything/intermediate/batch-2.json new file mode 100644 index 0000000..ba11c8f --- /dev/null +++ b/.understand-anything/intermediate/batch-2.json @@ -0,0 +1,943 @@ +{ + "nodes": [ + { + "id": "document:.agents/skills/lemana-vpn-operations/SKILL.md", + "type": "document", + "name": "Lemana VPN Operations skill", + "filePath": ".agents/skills/lemana-vpn-operations/SKILL.md", + "summary": "Инструкции для операционных задач: установка, обновление, статусы, безопасные ответы пользователю и live-connection правила.", + "tags": [ + "skill", + "operations", + "documentation" + ], + "complexity": "moderate" + }, + { + "id": "document:.agents/skills/lemana-vpn-sso-autofill/SKILL.md", + "type": "document", + "name": "Lemana VPN SSO skill", + "filePath": ".agents/skills/lemana-vpn-sso-autofill/SKILL.md", + "summary": "Инструкции по Bitwarden, Keychain, TOTP, Keycloak SSO, autofill и runtime-патчам openconnect-lite.", + "tags": [ + "skill", + "sso", + "credentials" + ], + "complexity": "moderate" + }, + { + "id": "document:.agents/skills/lemana-vpn-testing/SKILL.md", + "type": "document", + "name": "Lemana VPN Testing skill", + "filePath": ".agents/skills/lemana-vpn-testing/SKILL.md", + "summary": "Инструкции по non-live проверкам, status checks, live VPN safety и финальному отчету для изменений в репозитории.", + "tags": [ + "skill", + "testing", + "verification" + ], + "complexity": "moderate" + }, + { + "id": "file:app/Package.swift", + "type": "file", + "name": "Package.swift", + "filePath": "app/Package.swift", + "summary": "Swift Package manifest для сборки executable target LemanaVPN под macOS 13+.", + "tags": [ + "swift", + "build-system", + "menu-bar-app" + ], + "complexity": "simple" + }, + { + "id": "file:app/Sources/LemanaVPN/LemanaVPNApp.swift", + "type": "file", + "name": "LemanaVPNApp.swift", + "filePath": "app/Sources/LemanaVPN/LemanaVPNApp.swift", + "summary": "SwiftUI/AppKit menu bar surface: отображает состояние VPN, кнопки подключения, логи и действия пользователя.", + "tags": [ + "swiftui", + "menu-bar", + "ui" + ], + "complexity": "moderate" + }, + { + "id": "class:app/Sources/LemanaVPN/LemanaVPNApp.swift:LemanaVPNApp", + "type": "class", + "name": "LemanaVPNApp", + "filePath": "app/Sources/LemanaVPN/LemanaVPNApp.swift", + "lineRange": [ + 5, + 16 + ], + "summary": "Точка входа SwiftUI menu bar app, связывает VPNManager с MenuBarExtra.", + "tags": [ + "swift", + "type", + "menu-bar-app" + ], + "complexity": "simple" + }, + { + "id": "class:app/Sources/LemanaVPN/LemanaVPNApp.swift:StatusBarLabel", + "type": "class", + "name": "StatusBarLabel", + "filePath": "app/Sources/LemanaVPN/LemanaVPNApp.swift", + "lineRange": [ + 17, + 51 + ], + "summary": "Компактный status bar label с иконкой и оставшимся временем сессии.", + "tags": [ + "swift", + "type", + "menu-bar-app" + ], + "complexity": "simple" + }, + { + "id": "class:app/Sources/LemanaVPN/LemanaVPNApp.swift:VPNMenuView", + "type": "class", + "name": "VPNMenuView", + "filePath": "app/Sources/LemanaVPN/LemanaVPNApp.swift", + "lineRange": [ + 52, + 191 + ], + "summary": "Основной menu UI с командами auto/manual/manual-full connect, disconnect, logs и quit.", + "tags": [ + "swift", + "type", + "menu-bar-app" + ], + "complexity": "moderate" + }, + { + "id": "file:app/Sources/LemanaVPN/VPNManager.swift", + "type": "file", + "name": "VPNManager.swift", + "filePath": "app/Sources/LemanaVPN/VPNManager.swift", + "summary": "Основной state manager Swift-приложения: запускает CLI в JSON mode, декодирует события, отслеживает health и управляет reconnect/disconnect.", + "tags": [ + "swift", + "state-manager", + "cli-bridge" + ], + "complexity": "complex" + }, + { + "id": "class:app/Sources/LemanaVPN/VPNManager.swift:VPNEvent", + "type": "class", + "name": "VPNEvent", + "filePath": "app/Sources/LemanaVPN/VPNManager.swift", + "lineRange": [ + 4, + 17 + ], + "summary": "Decodable-модель JSON events, которые runtime CLI отправляет в Swift app.", + "tags": [ + "swift", + "type", + "menu-bar-app" + ], + "complexity": "simple" + }, + { + "id": "class:app/Sources/LemanaVPN/VPNManager.swift:VPNStatusResponse", + "type": "class", + "name": "VPNStatusResponse", + "filePath": "app/Sources/LemanaVPN/VPNManager.swift", + "lineRange": [ + 18, + 21 + ], + "summary": "Decodable envelope для ответа `vpn --status --json`.", + "tags": [ + "swift", + "type", + "menu-bar-app" + ], + "complexity": "simple" + }, + { + "id": "class:app/Sources/LemanaVPN/VPNManager.swift:ModuleStatus", + "type": "class", + "name": "ModuleStatus", + "filePath": "app/Sources/LemanaVPN/VPNManager.swift", + "lineRange": [ + 22, + 101 + ], + "summary": "Модель статуса модулей и вычисление summary/systemImage для menu bar app.", + "tags": [ + "swift", + "type", + "menu-bar-app" + ], + "complexity": "moderate" + }, + { + "id": "class:app/Sources/LemanaVPN/VPNManager.swift:VPNState", + "type": "class", + "name": "VPNState", + "filePath": "app/Sources/LemanaVPN/VPNManager.swift", + "lineRange": [ + 102, + 121 + ], + "summary": "Finite state model для disconnected/connecting/connected/reconnecting/error состояний.", + "tags": [ + "swift", + "type", + "menu-bar-app" + ], + "complexity": "simple" + }, + { + "id": "class:app/Sources/LemanaVPN/VPNManager.swift:VPNLaunchMode", + "type": "class", + "name": "VPNLaunchMode", + "filePath": "app/Sources/LemanaVPN/VPNManager.swift", + "lineRange": [ + 122, + 136 + ], + "summary": "Маппинг app launch modes на CLI flags `--auto`, `--manual`, `--manual-full`.", + "tags": [ + "swift", + "type", + "menu-bar-app" + ], + "complexity": "simple" + }, + { + "id": "class:app/Sources/LemanaVPN/VPNManager.swift:VPNManager", + "type": "class", + "name": "VPNManager", + "filePath": "app/Sources/LemanaVPN/VPNManager.swift", + "lineRange": [ + 137, + 698 + ], + "summary": "ObservableObject, который мостит Swift menu bar app с shell CLI, JSON events и health checks.", + "tags": [ + "swift", + "type", + "state-manager" + ], + "complexity": "complex" + }, + { + "id": "function:app/Sources/LemanaVPN/VPNManager.swift:refreshStatus", + "type": "function", + "name": "refreshStatus", + "filePath": "app/Sources/LemanaVPN/VPNManager.swift", + "lineRange": [ + 194, + 248 + ], + "summary": "Запускает CLI status JSON и обновляет module summary в menu bar app.", + "tags": [ + "function", + "runtime", + "refreshStatus" + ], + "complexity": "moderate" + }, + { + "id": "function:app/Sources/LemanaVPN/VPNManager.swift:connect", + "type": "function", + "name": "connect", + "filePath": "app/Sources/LemanaVPN/VPNManager.swift", + "lineRange": [ + 249, + 326 + ], + "summary": "Запускает CLI в JSON mode с выбранным режимом подключения и подписывается на stdout/stderr.", + "tags": [ + "function", + "runtime", + "connect" + ], + "complexity": "moderate" + }, + { + "id": "function:app/Sources/LemanaVPN/VPNManager.swift:disconnect", + "type": "function", + "name": "disconnect", + "filePath": "app/Sources/LemanaVPN/VPNManager.swift", + "lineRange": [ + 327, + 349 + ], + "summary": "Отправляет SIGINT текущему CLI process и останавливает reconnect state.", + "tags": [ + "function", + "runtime", + "disconnect" + ], + "complexity": "simple" + }, + { + "id": "function:app/Sources/LemanaVPN/VPNManager.swift:parseEvent", + "type": "function", + "name": "parseEvent", + "filePath": "app/Sources/LemanaVPN/VPNManager.swift", + "lineRange": [ + 350, + 448 + ], + "summary": "Декодирует JSON events из CLI и переводит их в Swift state updates.", + "tags": [ + "function", + "runtime", + "parseEvent" + ], + "complexity": "moderate" + }, + { + "id": "function:app/Sources/LemanaVPN/VPNManager.swift:handleTermination", + "type": "function", + "name": "handleTermination", + "filePath": "app/Sources/LemanaVPN/VPNManager.swift", + "lineRange": [ + 449, + 576 + ], + "summary": "Обрабатывает завершение CLI process и решает, нужен ли автоматический reconnect.", + "tags": [ + "function", + "runtime", + "handleTermination" + ], + "complexity": "moderate" + }, + { + "id": "function:app/Sources/LemanaVPN/VPNManager.swift:startHealthCheck", + "type": "function", + "name": "startHealthCheck", + "filePath": "app/Sources/LemanaVPN/VPNManager.swift", + "lineRange": [ + 577, + 666 + ], + "summary": "Периодически проверяет tunnel health и запускает reconnect при повторных failures.", + "tags": [ + "function", + "runtime", + "startHealthCheck" + ], + "complexity": "moderate" + }, + { + "id": "file:bin/vpn-lemanapro.sh", + "type": "file", + "name": "vpn-lemanapro.sh", + "filePath": "bin/vpn-lemanapro.sh", + "summary": "Главный runtime CLI: status JSON, sync credentials, Keychain/TOTP, runtime patches, SSO запуск, reconnect loop, DNS cleanup и фильтрация openconnect-lite output.", + "tags": [ + "cli", + "runtime", + "sso" + ], + "complexity": "complex" + }, + { + "id": "function:bin/vpn-lemanapro.sh:_module_bool", + "type": "function", + "name": "_module_bool", + "filePath": "bin/vpn-lemanapro.sh", + "lineRange": [ + 197, + 203 + ], + "summary": "Значимая функция _module_bool в bin/vpn-lemanapro.sh.", + "tags": [ + "function", + "runtime", + "module-bool" + ], + "complexity": "simple" + }, + { + "id": "function:bin/vpn-lemanapro.sh:_patches_active", + "type": "function", + "name": "_patches_active", + "filePath": "bin/vpn-lemanapro.sh", + "lineRange": [ + 205, + 219 + ], + "summary": "Значимая функция _patches_active в bin/vpn-lemanapro.sh.", + "tags": [ + "function", + "runtime", + "patches-active" + ], + "complexity": "simple" + }, + { + "id": "function:bin/vpn-lemanapro.sh:_module_status_json", + "type": "function", + "name": "_module_status_json", + "filePath": "bin/vpn-lemanapro.sh", + "lineRange": [ + 225, + 264 + ], + "summary": "Собирает машинно-читаемый JSON со статусом модулей, credentials, app, DNS и patches.", + "tags": [ + "function", + "runtime", + "module-status-json" + ], + "complexity": "simple" + }, + { + "id": "function:bin/vpn-lemanapro.sh:_module_human_part", + "type": "function", + "name": "_module_human_part", + "filePath": "bin/vpn-lemanapro.sh", + "lineRange": [ + 266, + 275 + ], + "summary": "Значимая функция _module_human_part в bin/vpn-lemanapro.sh.", + "tags": [ + "function", + "runtime", + "module-human-part" + ], + "complexity": "simple" + }, + { + "id": "function:bin/vpn-lemanapro.sh:_module_status_human", + "type": "function", + "name": "_module_status_human", + "filePath": "bin/vpn-lemanapro.sh", + "lineRange": [ + 277, + 311 + ], + "summary": "Печатает человекочитаемую строку статуса модулей перед подключением.", + "tags": [ + "function", + "runtime", + "module-status-human" + ], + "complexity": "simple" + }, + { + "id": "function:bin/vpn-lemanapro.sh:_check_status", + "type": "function", + "name": "_check_status", + "filePath": "bin/vpn-lemanapro.sh", + "lineRange": [ + 313, + 375 + ], + "summary": "Проверяет PID/status file, running process, DNS target и возвращает disconnected/connected JSON.", + "tags": [ + "function", + "runtime", + "check-status" + ], + "complexity": "moderate" + }, + { + "id": "function:bin/vpn-lemanapro.sh:_patch_oc", + "type": "function", + "name": "_patch_oc", + "filePath": "bin/vpn-lemanapro.sh", + "lineRange": [ + 377, + 604 + ], + "summary": "Применяет runtime patches к openconnect-lite для Keycloak autofill/manual/debug сценариев.", + "tags": [ + "function", + "runtime", + "patch-oc" + ], + "complexity": "complex" + }, + { + "id": "function:bin/vpn-lemanapro.sh:_normalize_totp_secret", + "type": "function", + "name": "_normalize_totp_secret", + "filePath": "bin/vpn-lemanapro.sh", + "lineRange": [ + 623, + 649 + ], + "summary": "Нормализует TOTP secret из raw secret или otpauth URI перед сохранением.", + "tags": [ + "function", + "runtime", + "normalize-totp-secret" + ], + "complexity": "simple" + }, + { + "id": "function:bin/vpn-lemanapro.sh:_persist_username", + "type": "function", + "name": "_persist_username", + "filePath": "bin/vpn-lemanapro.sh", + "lineRange": [ + 675, + 718 + ], + "summary": "Значимая функция _persist_username в bin/vpn-lemanapro.sh.", + "tags": [ + "function", + "runtime", + "persist-username" + ], + "complexity": "simple" + }, + { + "id": "function:bin/vpn-lemanapro.sh:_ensure_username", + "type": "function", + "name": "_ensure_username", + "filePath": "bin/vpn-lemanapro.sh", + "lineRange": [ + 720, + 745 + ], + "summary": "Значимая функция _ensure_username в bin/vpn-lemanapro.sh.", + "tags": [ + "function", + "runtime", + "ensure-username" + ], + "complexity": "simple" + }, + { + "id": "function:bin/vpn-lemanapro.sh:_configure_keychain", + "type": "function", + "name": "_configure_keychain", + "filePath": "bin/vpn-lemanapro.sh", + "lineRange": [ + 747, + 785 + ], + "summary": "Интерактивно сохраняет LDAP password и TOTP seed в macOS Keychain.", + "tags": [ + "function", + "runtime", + "configure-keychain" + ], + "complexity": "simple" + }, + { + "id": "function:bin/vpn-lemanapro.sh:_ensure_keychain_credentials", + "type": "function", + "name": "_ensure_keychain_credentials", + "filePath": "bin/vpn-lemanapro.sh", + "lineRange": [ + 787, + 814 + ], + "summary": "Проверяет наличие обязательных Keychain credentials перед auto/manual подключением.", + "tags": [ + "function", + "runtime", + "ensure-keychain-credentials" + ], + "complexity": "simple" + }, + { + "id": "function:bin/vpn-lemanapro.sh:_bw_unlock", + "type": "function", + "name": "_bw_unlock", + "filePath": "bin/vpn-lemanapro.sh", + "lineRange": [ + 823, + 889 + ], + "summary": "Открывает Bitwarden session с учетом Touch ID helper и fallback prompts.", + "tags": [ + "function", + "runtime", + "bw-unlock" + ], + "complexity": "moderate" + }, + { + "id": "function:bin/vpn-lemanapro.sh:_sync_bitwarden", + "type": "function", + "name": "_sync_bitwarden", + "filePath": "bin/vpn-lemanapro.sh", + "lineRange": [ + 891, + 925 + ], + "summary": "Синхронизирует LDAP password и TOTP seed из Bitwarden в macOS Keychain.", + "tags": [ + "function", + "runtime", + "sync-bitwarden" + ], + "complexity": "simple" + }, + { + "id": "function:bin/vpn-lemanapro.sh:_sync_credentials", + "type": "function", + "name": "_sync_credentials", + "filePath": "bin/vpn-lemanapro.sh", + "lineRange": [ + 927, + 936 + ], + "summary": "Выбирает credential source и запускает соответствующий sync/preflight путь.", + "tags": [ + "function", + "runtime", + "sync-credentials" + ], + "complexity": "simple" + }, + { + "id": "function:bin/vpn-lemanapro.sh:_dns_cleanup", + "type": "function", + "name": "_dns_cleanup", + "filePath": "bin/vpn-lemanapro.sh", + "lineRange": [ + 938, + 954 + ], + "summary": "Вызывает narrow sudo wrapper для очистки VPN DNS при завершении сессии.", + "tags": [ + "function", + "runtime", + "dns-cleanup" + ], + "complexity": "simple" + }, + { + "id": "function:bin/vpn-lemanapro.sh:_filter_output", + "type": "function", + "name": "_filter_output", + "filePath": "bin/vpn-lemanapro.sh", + "lineRange": [ + 956, + 1006 + ], + "summary": "Фильтрует поток openconnect-lite, извлекает IP/expiry, пишет status JSON и события для app.", + "tags": [ + "function", + "runtime", + "filter-output" + ], + "complexity": "moderate" + }, + { + "id": "document:docs/goals/manual-first-auth-flow/GOAL.md", + "type": "document", + "name": "Manual-first auth goal", + "filePath": "docs/goals/manual-first-auth-flow/GOAL.md", + "summary": "Краткое описание цели manual-first authentication flow и ожидаемого пользовательского результата.", + "tags": [ + "goal", + "auth-flow", + "planning" + ], + "complexity": "simple" + }, + { + "id": "document:docs/goals/manual-first-auth-flow/PLAN.md", + "type": "document", + "name": "Manual-first auth plan", + "filePath": "docs/goals/manual-first-auth-flow/PLAN.md", + "summary": "Подробный план изменения installer UX, CLI auth profiles, Swift menu-bar surface и smoke coverage для manual-first сценария.", + "tags": [ + "plan", + "auth-flow", + "implementation" + ], + "complexity": "complex" + }, + { + "id": "document:docs/superpowers/plans/2026-05-24-keychain-totp-provider.md", + "type": "document", + "name": "Keychain TOTP provider plan", + "filePath": "docs/superpowers/plans/2026-05-24-keychain-totp-provider.md", + "summary": "Исторический implementation plan для разделения credential sources, нормализации TOTP и расширения smoke-проверок.", + "tags": [ + "plan", + "keychain", + "totp" + ], + "complexity": "complex" + }, + { + "id": "file:libexec/lemana-vpn-dns-cleanup", + "type": "file", + "name": "lemana-vpn-dns-cleanup", + "filePath": "libexec/lemana-vpn-dns-cleanup", + "summary": "Узкий root-owned wrapper, который сбрасывает только корпоративные DNS и обновляет macOS resolver cache.", + "tags": [ + "dns", + "sudo-wrapper", + "cleanup" + ], + "complexity": "simple" + }, + { + "id": "config:templates/openconnect-lite-config.toml", + "type": "config", + "name": "openconnect-lite-config.toml", + "filePath": "templates/openconnect-lite-config.toml", + "summary": "Шаблон профиля openconnect-lite: gateway, credentials mode и Keycloak auto-fill selectors для username, password и TOTP.", + "tags": [ + "configuration", + "openconnect-lite", + "autofill" + ], + "complexity": "simple" + }, + { + "id": "file:tests/smoke.sh", + "type": "file", + "name": "smoke.sh", + "filePath": "tests/smoke.sh", + "summary": "Non-live smoke suite: shell syntax, installer/uninstaller checks, Swift wiring, SSO patch snippets, config selectors и regression assertions.", + "tags": [ + "test", + "smoke", + "verification" + ], + "complexity": "complex" + } + ], + "edges": [ + { + "source": "file:app/Sources/LemanaVPN/LemanaVPNApp.swift", + "target": "class:app/Sources/LemanaVPN/LemanaVPNApp.swift:LemanaVPNApp", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:app/Sources/LemanaVPN/LemanaVPNApp.swift", + "target": "class:app/Sources/LemanaVPN/LemanaVPNApp.swift:StatusBarLabel", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:app/Sources/LemanaVPN/LemanaVPNApp.swift", + "target": "class:app/Sources/LemanaVPN/LemanaVPNApp.swift:VPNMenuView", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:app/Sources/LemanaVPN/VPNManager.swift", + "target": "class:app/Sources/LemanaVPN/VPNManager.swift:VPNEvent", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:app/Sources/LemanaVPN/VPNManager.swift", + "target": "class:app/Sources/LemanaVPN/VPNManager.swift:VPNStatusResponse", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:app/Sources/LemanaVPN/VPNManager.swift", + "target": "class:app/Sources/LemanaVPN/VPNManager.swift:ModuleStatus", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:app/Sources/LemanaVPN/VPNManager.swift", + "target": "class:app/Sources/LemanaVPN/VPNManager.swift:VPNState", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:app/Sources/LemanaVPN/VPNManager.swift", + "target": "class:app/Sources/LemanaVPN/VPNManager.swift:VPNLaunchMode", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:app/Sources/LemanaVPN/VPNManager.swift", + "target": "class:app/Sources/LemanaVPN/VPNManager.swift:VPNManager", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:app/Sources/LemanaVPN/VPNManager.swift", + "target": "function:app/Sources/LemanaVPN/VPNManager.swift:refreshStatus", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:app/Sources/LemanaVPN/VPNManager.swift", + "target": "function:app/Sources/LemanaVPN/VPNManager.swift:connect", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:app/Sources/LemanaVPN/VPNManager.swift", + "target": "function:app/Sources/LemanaVPN/VPNManager.swift:disconnect", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:app/Sources/LemanaVPN/VPNManager.swift", + "target": "function:app/Sources/LemanaVPN/VPNManager.swift:parseEvent", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:app/Sources/LemanaVPN/VPNManager.swift", + "target": "function:app/Sources/LemanaVPN/VPNManager.swift:handleTermination", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:app/Sources/LemanaVPN/VPNManager.swift", + "target": "function:app/Sources/LemanaVPN/VPNManager.swift:startHealthCheck", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:bin/vpn-lemanapro.sh", + "target": "function:bin/vpn-lemanapro.sh:_module_bool", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:bin/vpn-lemanapro.sh", + "target": "function:bin/vpn-lemanapro.sh:_patches_active", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:bin/vpn-lemanapro.sh", + "target": "function:bin/vpn-lemanapro.sh:_module_status_json", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:bin/vpn-lemanapro.sh", + "target": "function:bin/vpn-lemanapro.sh:_module_human_part", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:bin/vpn-lemanapro.sh", + "target": "function:bin/vpn-lemanapro.sh:_module_status_human", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:bin/vpn-lemanapro.sh", + "target": "function:bin/vpn-lemanapro.sh:_check_status", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:bin/vpn-lemanapro.sh", + "target": "function:bin/vpn-lemanapro.sh:_patch_oc", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:bin/vpn-lemanapro.sh", + "target": "function:bin/vpn-lemanapro.sh:_normalize_totp_secret", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:bin/vpn-lemanapro.sh", + "target": "function:bin/vpn-lemanapro.sh:_persist_username", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:bin/vpn-lemanapro.sh", + "target": "function:bin/vpn-lemanapro.sh:_ensure_username", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:bin/vpn-lemanapro.sh", + "target": "function:bin/vpn-lemanapro.sh:_configure_keychain", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:bin/vpn-lemanapro.sh", + "target": "function:bin/vpn-lemanapro.sh:_ensure_keychain_credentials", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:bin/vpn-lemanapro.sh", + "target": "function:bin/vpn-lemanapro.sh:_bw_unlock", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:bin/vpn-lemanapro.sh", + "target": "function:bin/vpn-lemanapro.sh:_sync_bitwarden", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:bin/vpn-lemanapro.sh", + "target": "function:bin/vpn-lemanapro.sh:_sync_credentials", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:bin/vpn-lemanapro.sh", + "target": "function:bin/vpn-lemanapro.sh:_dns_cleanup", + "type": "contains", + "direction": "forward", + "weight": 1 + }, + { + "source": "file:bin/vpn-lemanapro.sh", + "target": "function:bin/vpn-lemanapro.sh:_filter_output", + "type": "contains", + "direction": "forward", + "weight": 1 + } + ] +} \ No newline at end of file diff --git a/.understand-anything/intermediate/batches.json b/.understand-anything/intermediate/batches.json new file mode 100644 index 0000000..03df628 --- /dev/null +++ b/.understand-anything/intermediate/batches.json @@ -0,0 +1,149 @@ +{ + "schemaVersion": 1, + "algorithm": "louvain", + "totalFiles": 17, + "totalBatches": 2, + "exportsByPath": { + "app/Package.swift": [], + "app/Sources/LemanaVPN/LemanaVPNApp.swift": [], + "app/Sources/LemanaVPN/VPNManager.swift": [], + "libexec/lemana-vpn-dns-cleanup": [] + }, + "batches": [ + { + "batchIndex": 1, + "files": [ + { + "path": "AGENTS.md", + "language": "markdown", + "sizeLines": 88, + "fileCategory": "docs" + }, + { + "path": "README.md", + "language": "markdown", + "sizeLines": 579, + "fileCategory": "docs" + }, + { + "path": "install.sh", + "language": "shell", + "sizeLines": 943, + "fileCategory": "script" + }, + { + "path": "uninstall.sh", + "language": "shell", + "sizeLines": 320, + "fileCategory": "script" + } + ], + "batchImportData": { + "AGENTS.md": [], + "README.md": [], + "install.sh": [], + "uninstall.sh": [] + }, + "neighborMap": {} + }, + { + "batchIndex": 2, + "files": [ + { + "path": ".agents/skills/lemana-vpn-operations/SKILL.md", + "language": "markdown", + "sizeLines": 70, + "fileCategory": "docs" + }, + { + "path": ".agents/skills/lemana-vpn-sso-autofill/SKILL.md", + "language": "markdown", + "sizeLines": 80, + "fileCategory": "docs" + }, + { + "path": ".agents/skills/lemana-vpn-testing/SKILL.md", + "language": "markdown", + "sizeLines": 101, + "fileCategory": "docs" + }, + { + "path": "app/Package.swift", + "language": "swift", + "sizeLines": 14, + "fileCategory": "code" + }, + { + "path": "app/Sources/LemanaVPN/LemanaVPNApp.swift", + "language": "swift", + "sizeLines": 190, + "fileCategory": "code" + }, + { + "path": "app/Sources/LemanaVPN/VPNManager.swift", + "language": "swift", + "sizeLines": 697, + "fileCategory": "code" + }, + { + "path": "bin/vpn-lemanapro.sh", + "language": "shell", + "sizeLines": 1090, + "fileCategory": "script" + }, + { + "path": "docs/goals/manual-first-auth-flow/GOAL.md", + "language": "markdown", + "sizeLines": 12, + "fileCategory": "docs" + }, + { + "path": "docs/goals/manual-first-auth-flow/PLAN.md", + "language": "markdown", + "sizeLines": 251, + "fileCategory": "docs" + }, + { + "path": "docs/superpowers/plans/2026-05-24-keychain-totp-provider.md", + "language": "markdown", + "sizeLines": 935, + "fileCategory": "docs" + }, + { + "path": "libexec/lemana-vpn-dns-cleanup", + "language": "unknown", + "sizeLines": 23, + "fileCategory": "code" + }, + { + "path": "templates/openconnect-lite-config.toml", + "language": "toml", + "sizeLines": 32, + "fileCategory": "config" + }, + { + "path": "tests/smoke.sh", + "language": "shell", + "sizeLines": 438, + "fileCategory": "script" + } + ], + "batchImportData": { + ".agents/skills/lemana-vpn-operations/SKILL.md": [], + ".agents/skills/lemana-vpn-sso-autofill/SKILL.md": [], + ".agents/skills/lemana-vpn-testing/SKILL.md": [], + "app/Package.swift": [], + "app/Sources/LemanaVPN/LemanaVPNApp.swift": [], + "app/Sources/LemanaVPN/VPNManager.swift": [], + "bin/vpn-lemanapro.sh": [], + "docs/goals/manual-first-auth-flow/GOAL.md": [], + "docs/goals/manual-first-auth-flow/PLAN.md": [], + "docs/superpowers/plans/2026-05-24-keychain-totp-provider.md": [], + "libexec/lemana-vpn-dns-cleanup": [], + "templates/openconnect-lite-config.toml": [], + "tests/smoke.sh": [] + }, + "neighborMap": {} + } + ] +} \ No newline at end of file diff --git a/.understand-anything/intermediate/import-input.json b/.understand-anything/intermediate/import-input.json new file mode 100644 index 0000000..ba7a237 --- /dev/null +++ b/.understand-anything/intermediate/import-input.json @@ -0,0 +1,107 @@ +{ + "projectRoot": "/Users/dokril/git/home/lemana-vpn", + "files": [ + { + "path": ".agents/skills/lemana-vpn-operations/SKILL.md", + "language": "markdown", + "sizeLines": 70, + "fileCategory": "docs" + }, + { + "path": ".agents/skills/lemana-vpn-sso-autofill/SKILL.md", + "language": "markdown", + "sizeLines": 80, + "fileCategory": "docs" + }, + { + "path": ".agents/skills/lemana-vpn-testing/SKILL.md", + "language": "markdown", + "sizeLines": 101, + "fileCategory": "docs" + }, + { + "path": "AGENTS.md", + "language": "markdown", + "sizeLines": 88, + "fileCategory": "docs" + }, + { + "path": "app/Package.swift", + "language": "swift", + "sizeLines": 14, + "fileCategory": "code" + }, + { + "path": "app/Sources/LemanaVPN/LemanaVPNApp.swift", + "language": "swift", + "sizeLines": 190, + "fileCategory": "code" + }, + { + "path": "app/Sources/LemanaVPN/VPNManager.swift", + "language": "swift", + "sizeLines": 697, + "fileCategory": "code" + }, + { + "path": "bin/vpn-lemanapro.sh", + "language": "shell", + "sizeLines": 1090, + "fileCategory": "script" + }, + { + "path": "docs/goals/manual-first-auth-flow/GOAL.md", + "language": "markdown", + "sizeLines": 12, + "fileCategory": "docs" + }, + { + "path": "docs/goals/manual-first-auth-flow/PLAN.md", + "language": "markdown", + "sizeLines": 251, + "fileCategory": "docs" + }, + { + "path": "docs/superpowers/plans/2026-05-24-keychain-totp-provider.md", + "language": "markdown", + "sizeLines": 935, + "fileCategory": "docs" + }, + { + "path": "install.sh", + "language": "shell", + "sizeLines": 943, + "fileCategory": "script" + }, + { + "path": "libexec/lemana-vpn-dns-cleanup", + "language": "unknown", + "sizeLines": 23, + "fileCategory": "code" + }, + { + "path": "README.md", + "language": "markdown", + "sizeLines": 579, + "fileCategory": "docs" + }, + { + "path": "templates/openconnect-lite-config.toml", + "language": "toml", + "sizeLines": 32, + "fileCategory": "config" + }, + { + "path": "tests/smoke.sh", + "language": "shell", + "sizeLines": 438, + "fileCategory": "script" + }, + { + "path": "uninstall.sh", + "language": "shell", + "sizeLines": 320, + "fileCategory": "script" + } + ] +} \ No newline at end of file diff --git a/.understand-anything/intermediate/import-map.json b/.understand-anything/intermediate/import-map.json new file mode 100644 index 0000000..3e11446 --- /dev/null +++ b/.understand-anything/intermediate/import-map.json @@ -0,0 +1,27 @@ +{ + "scriptCompleted": true, + "stats": { + "filesScanned": 17, + "filesWithImports": 0, + "totalEdges": 0 + }, + "importMap": { + ".agents/skills/lemana-vpn-operations/SKILL.md": [], + ".agents/skills/lemana-vpn-sso-autofill/SKILL.md": [], + ".agents/skills/lemana-vpn-testing/SKILL.md": [], + "AGENTS.md": [], + "app/Package.swift": [], + "app/Sources/LemanaVPN/LemanaVPNApp.swift": [], + "app/Sources/LemanaVPN/VPNManager.swift": [], + "bin/vpn-lemanapro.sh": [], + "docs/goals/manual-first-auth-flow/GOAL.md": [], + "docs/goals/manual-first-auth-flow/PLAN.md": [], + "docs/superpowers/plans/2026-05-24-keychain-totp-provider.md": [], + "install.sh": [], + "libexec/lemana-vpn-dns-cleanup": [], + "README.md": [], + "templates/openconnect-lite-config.toml": [], + "tests/smoke.sh": [], + "uninstall.sh": [] + } +} \ No newline at end of file diff --git a/.understand-anything/intermediate/scan-raw.json b/.understand-anything/intermediate/scan-raw.json new file mode 100644 index 0000000..0b180d6 --- /dev/null +++ b/.understand-anything/intermediate/scan-raw.json @@ -0,0 +1,126 @@ +{ + "scriptCompleted": true, + "files": [ + { + "path": ".agents/skills/lemana-vpn-operations/SKILL.md", + "language": "markdown", + "sizeLines": 70, + "fileCategory": "docs" + }, + { + "path": ".agents/skills/lemana-vpn-sso-autofill/SKILL.md", + "language": "markdown", + "sizeLines": 80, + "fileCategory": "docs" + }, + { + "path": ".agents/skills/lemana-vpn-testing/SKILL.md", + "language": "markdown", + "sizeLines": 101, + "fileCategory": "docs" + }, + { + "path": "AGENTS.md", + "language": "markdown", + "sizeLines": 88, + "fileCategory": "docs" + }, + { + "path": "app/Package.swift", + "language": "swift", + "sizeLines": 14, + "fileCategory": "code" + }, + { + "path": "app/Sources/LemanaVPN/LemanaVPNApp.swift", + "language": "swift", + "sizeLines": 190, + "fileCategory": "code" + }, + { + "path": "app/Sources/LemanaVPN/VPNManager.swift", + "language": "swift", + "sizeLines": 697, + "fileCategory": "code" + }, + { + "path": "bin/vpn-lemanapro.sh", + "language": "shell", + "sizeLines": 1090, + "fileCategory": "script" + }, + { + "path": "docs/goals/manual-first-auth-flow/GOAL.md", + "language": "markdown", + "sizeLines": 12, + "fileCategory": "docs" + }, + { + "path": "docs/goals/manual-first-auth-flow/PLAN.md", + "language": "markdown", + "sizeLines": 251, + "fileCategory": "docs" + }, + { + "path": "docs/superpowers/plans/2026-05-24-keychain-totp-provider.md", + "language": "markdown", + "sizeLines": 935, + "fileCategory": "docs" + }, + { + "path": "install.sh", + "language": "shell", + "sizeLines": 943, + "fileCategory": "script" + }, + { + "path": "libexec/lemana-vpn-dns-cleanup", + "language": "unknown", + "sizeLines": 23, + "fileCategory": "code" + }, + { + "path": "README.md", + "language": "markdown", + "sizeLines": 579, + "fileCategory": "docs" + }, + { + "path": "templates/openconnect-lite-config.toml", + "language": "toml", + "sizeLines": 32, + "fileCategory": "config" + }, + { + "path": "tests/smoke.sh", + "language": "shell", + "sizeLines": 438, + "fileCategory": "script" + }, + { + "path": "uninstall.sh", + "language": "shell", + "sizeLines": 320, + "fileCategory": "script" + } + ], + "totalFiles": 17, + "filteredByIgnore": 3, + "estimatedComplexity": "small", + "stats": { + "filesScanned": 17, + "byCategory": { + "docs": 8, + "code": 4, + "script": 4, + "config": 1 + }, + "byLanguage": { + "markdown": 8, + "swift": 3, + "shell": 4, + "unknown": 1, + "toml": 1 + } + } +} \ No newline at end of file diff --git a/.understand-anything/intermediate/scan-result.json b/.understand-anything/intermediate/scan-result.json new file mode 100644 index 0000000..00cc266 --- /dev/null +++ b/.understand-anything/intermediate/scan-result.json @@ -0,0 +1,163 @@ +{ + "name": "Lemana VPN", + "projectName": "Lemana VPN", + "description": "macOS-пакет для корпоративного VPN Lemana: installer, CLI runtime, SSO/autofill через openconnect-lite, DNS cleanup wrapper и Swift menu bar app.", + "rawDescription": "CLI-установка корпоративного VPN vpn.lemanapro.ru для macOS.", + "readmeHead": "# Lemana VPN\n\nCLI-установка корпоративного VPN `vpn.lemanapro.ru` для macOS.\n\n**Модули по умолчанию:** Core: включён; Bitwarden: включён; Touch ID: включён; DNS cleanup: включён; Swift Menu Bar app: включён; автозапуск приложения: включён; runtime-патчи: применяются автоматически перед подключением.\n\n**Credential sources:** `bitwarden` синхронизирует LDAP-пароль и TOTP seed из Bitwarden в macOS Keychain; `keychain` хранит LDAP-пароль и постоянный TOTP seed напрямую в macOS Keychain. Оба источника используют один и тот же runtime `openconnect-lite` для SSO/autofill.\n\nРепозиторий собирает в один воспроизводимый пакет то, что раньше было ручной локальной настройкой:\n\n- `openconnect` как VPN-клиент;\n- `openconnect-lite` для SAML SSO через Keycloak;\n- опциональный Bitwarden CLI для LDAP-пароля и TOTP seed;\n- опциональный Touch ID helper для мастер-пароля Bitwarden;\n- Swift Menu Bar app `LemanaVPN.app`;\n- безопасный DNS cleanup через root-owned wrapper;\n- алиасы `vpn`, `vpn-auto`, `vpn-manual`, `vpn-manual-full`, `vpn-debug`, `vpn-fix-dns`.\n\n## Быстрая установка\n\n```sh\ncurl -fsSL https://git.dokops.ru/dokril/lemana-vpn/raw/branch/main/install.sh | sh\n```\n\nЕсли установка запущена из терминала, скрипт работает как интерактивный wizard: проверит, что уже стоит, спросит как хранить credentials, предложит нужные модули и проведёт через настройку.\n\nПосле установки открой новый shell или выполни:\n\n```sh\nexec zsh\nvpn\n```\n\n## Интерактивная установка\n\nОбычный путь — запустить installer без флагов и ответить на вопросы:\n\n```sh\ncurl -fsSL https://git.dokops.ru/dokril/lemana-vpn/raw/branch/main/install.sh | sh\n```\n\nПервый важный вопрос — как хранить VPN credentials:\n\n```text\nКак хранить VPN credentials?\n 1) Bitwarden -> macOS Keychain\n 2) macOS Keychain: ввести LDAP password и TOTP seed сейчас\n 3) macOS Keychain: настрою вручную позже\nВыбор [1/2/3, Enter=1]:\n```\n\nЧто означают варианты:\n\n- `1` — использовать Bitwarden как sync-provider: installer поставит/проверит `bw`, а при запуске `vpn` CLI переложит LDAP password и TOTP seed из Bitwarden в macOS Keychain.\n- `2` — бесплатный Keychain-only путь: после установки CLI спросит LDAP password и постоянный TOTP seed или `otpauth://...secret=...`, затем сохранит их в macOS Keychain.\n- `3` — поставить CLI/app сейчас, а credentials настроить позже командой `vpn --configure-keychain`.\n\nДальше installer спросит только про системные модули: Touch ID для Bitwarden, sudoers, aliases, Swift Menu Bar app и автозапуск.\n\nФлаги остаются для CI, повторяемых установок и диагностики. Для обычной установки они не нужны.\n\nПроверить действия без изменений:\n\n```sh\ncurl -fsSL https://git.dokops.ru/dokril/lemana-vpn/raw/branch/main/install.sh | sh -s -- --dry-run\n```\n\nПример неинтерактивного режима для автоматизации:\n\n```sh\ncurl -fsSL https://git.dokops.ru/dokril/lemana-vpn/raw/branch/main/install.sh \\\n | sh -s -- --non-interactive --credential-source keychain --configure-keychain\n```\n\nПринудительно включить интерактивные вопросы:\n\n```sh\n", + "languages": [ + "markdown", + "swift", + "shell", + "unknown", + "toml" + ], + "frameworks": [ + "SwiftUI", + "AppKit", + "openconnect-lite", + "Bitwarden CLI", + "macOS Keychain" + ], + "files": [ + { + "path": ".agents/skills/lemana-vpn-operations/SKILL.md", + "language": "markdown", + "sizeLines": 70, + "fileCategory": "docs" + }, + { + "path": ".agents/skills/lemana-vpn-sso-autofill/SKILL.md", + "language": "markdown", + "sizeLines": 80, + "fileCategory": "docs" + }, + { + "path": ".agents/skills/lemana-vpn-testing/SKILL.md", + "language": "markdown", + "sizeLines": 101, + "fileCategory": "docs" + }, + { + "path": "AGENTS.md", + "language": "markdown", + "sizeLines": 88, + "fileCategory": "docs" + }, + { + "path": "app/Package.swift", + "language": "swift", + "sizeLines": 14, + "fileCategory": "code" + }, + { + "path": "app/Sources/LemanaVPN/LemanaVPNApp.swift", + "language": "swift", + "sizeLines": 190, + "fileCategory": "code" + }, + { + "path": "app/Sources/LemanaVPN/VPNManager.swift", + "language": "swift", + "sizeLines": 697, + "fileCategory": "code" + }, + { + "path": "bin/vpn-lemanapro.sh", + "language": "shell", + "sizeLines": 1090, + "fileCategory": "script" + }, + { + "path": "docs/goals/manual-first-auth-flow/GOAL.md", + "language": "markdown", + "sizeLines": 12, + "fileCategory": "docs" + }, + { + "path": "docs/goals/manual-first-auth-flow/PLAN.md", + "language": "markdown", + "sizeLines": 251, + "fileCategory": "docs" + }, + { + "path": "docs/superpowers/plans/2026-05-24-keychain-totp-provider.md", + "language": "markdown", + "sizeLines": 935, + "fileCategory": "docs" + }, + { + "path": "install.sh", + "language": "shell", + "sizeLines": 943, + "fileCategory": "script" + }, + { + "path": "libexec/lemana-vpn-dns-cleanup", + "language": "unknown", + "sizeLines": 23, + "fileCategory": "code" + }, + { + "path": "README.md", + "language": "markdown", + "sizeLines": 579, + "fileCategory": "docs" + }, + { + "path": "templates/openconnect-lite-config.toml", + "language": "toml", + "sizeLines": 32, + "fileCategory": "config" + }, + { + "path": "tests/smoke.sh", + "language": "shell", + "sizeLines": 438, + "fileCategory": "script" + }, + { + "path": "uninstall.sh", + "language": "shell", + "sizeLines": 320, + "fileCategory": "script" + } + ], + "totalFiles": 17, + "filteredByIgnore": 3, + "estimatedComplexity": "small", + "stats": { + "filesScanned": 17, + "byCategory": { + "docs": 8, + "code": 4, + "script": 4, + "config": 1 + }, + "byLanguage": { + "markdown": 8, + "swift": 3, + "shell": 4, + "unknown": 1, + "toml": 1 + } + }, + "importMap": { + ".agents/skills/lemana-vpn-operations/SKILL.md": [], + ".agents/skills/lemana-vpn-sso-autofill/SKILL.md": [], + ".agents/skills/lemana-vpn-testing/SKILL.md": [], + "AGENTS.md": [], + "app/Package.swift": [], + "app/Sources/LemanaVPN/LemanaVPNApp.swift": [], + "app/Sources/LemanaVPN/VPNManager.swift": [], + "bin/vpn-lemanapro.sh": [], + "docs/goals/manual-first-auth-flow/GOAL.md": [], + "docs/goals/manual-first-auth-flow/PLAN.md": [], + "docs/superpowers/plans/2026-05-24-keychain-totp-provider.md": [], + "install.sh": [], + "libexec/lemana-vpn-dns-cleanup": [], + "README.md": [], + "templates/openconnect-lite-config.toml": [], + "tests/smoke.sh": [], + "uninstall.sh": [] + } +} \ No newline at end of file diff --git a/.understand-anything/tmp/build-batches.cjs b/.understand-anything/tmp/build-batches.cjs new file mode 100644 index 0000000..23c39db --- /dev/null +++ b/.understand-anything/tmp/build-batches.cjs @@ -0,0 +1,206 @@ +const fs = require("fs"); +const path = require("path"); + +const root = process.cwd(); +const inter = path.join(root, ".understand-anything", "intermediate"); +const tmp = path.join(root, ".understand-anything", "tmp"); +const scan = JSON.parse(fs.readFileSync(path.join(inter, "scan-result.json"), "utf8")); +const batches = JSON.parse(fs.readFileSync(path.join(inter, "batches.json"), "utf8")).batches; + +const fileInfo = new Map(scan.files.map((f) => [f.path, f])); +const fileNodeId = (file) => { + if (file.fileCategory === "docs") return `document:${file.path}`; + if (file.fileCategory === "config") return `config:${file.path}`; + return `file:${file.path}`; +}; +const edge = (source, target, type, weight) => ({ source, target, type, direction: "forward", weight }); +const complexity = (lines) => (lines > 200 ? "complex" : lines > 50 ? "moderate" : "simple"); +const nameOf = (p) => p.split("/").pop(); + +const fileMeta = { + ".agents/skills/lemana-vpn-operations/SKILL.md": ["Lemana VPN Operations skill", "Инструкции для операционных задач: установка, обновление, статусы, безопасные ответы пользователю и live-connection правила.", ["skill", "operations", "documentation"]], + ".agents/skills/lemana-vpn-sso-autofill/SKILL.md": ["Lemana VPN SSO skill", "Инструкции по Bitwarden, Keychain, TOTP, Keycloak SSO, autofill и runtime-патчам openconnect-lite.", ["skill", "sso", "credentials"]], + ".agents/skills/lemana-vpn-testing/SKILL.md": ["Lemana VPN Testing skill", "Инструкции по non-live проверкам, status checks, live VPN safety и финальному отчету для изменений в репозитории.", ["skill", "testing", "verification"]], + "AGENTS.md": ["AGENTS.md", "Репозиторные правила для агентов: модель системы, live VPN safety, тестовая лестница и контракт финального ответа.", ["agent-rules", "documentation", "safety"]], + "README.md": ["README.md", "Основная пользовательская документация Lemana VPN: установка, credential sources, модули, использование, диагностика и удаление.", ["documentation", "overview", "user-guide"]], + "docs/goals/manual-first-auth-flow/GOAL.md": ["Manual-first auth goal", "Краткое описание цели manual-first authentication flow и ожидаемого пользовательского результата.", ["goal", "auth-flow", "planning"]], + "docs/goals/manual-first-auth-flow/PLAN.md": ["Manual-first auth plan", "Подробный план изменения installer UX, CLI auth profiles, Swift menu-bar surface и smoke coverage для manual-first сценария.", ["plan", "auth-flow", "implementation"]], + "docs/superpowers/plans/2026-05-24-keychain-totp-provider.md": ["Keychain TOTP provider plan", "Исторический implementation plan для разделения credential sources, нормализации TOTP и расширения smoke-проверок.", ["plan", "keychain", "totp"]], + "app/Package.swift": ["Package.swift", "Swift Package manifest для сборки executable target LemanaVPN под macOS 13+.", ["swift", "build-system", "menu-bar-app"]], + "app/Sources/LemanaVPN/LemanaVPNApp.swift": ["LemanaVPNApp.swift", "SwiftUI/AppKit menu bar surface: отображает состояние VPN, кнопки подключения, логи и действия пользователя.", ["swiftui", "menu-bar", "ui"]], + "app/Sources/LemanaVPN/VPNManager.swift": ["VPNManager.swift", "Основной state manager Swift-приложения: запускает CLI в JSON mode, декодирует события, отслеживает health и управляет reconnect/disconnect.", ["swift", "state-manager", "cli-bridge"]], + "bin/vpn-lemanapro.sh": ["vpn-lemanapro.sh", "Главный runtime CLI: status JSON, sync credentials, Keychain/TOTP, runtime patches, SSO запуск, reconnect loop, DNS cleanup и фильтрация openconnect-lite output.", ["cli", "runtime", "sso"]], + "install.sh": ["install.sh", "Installer wizard для Homebrew/pipx/openconnect-lite, CLI, config, DNS wrapper, sudoers, Touch ID helper, Swift app, launch agent и shell aliases.", ["installer", "wizard", "macos"]], + "uninstall.sh": ["uninstall.sh", "Uninstaller для восстановления runtime patch backups, удаления CLI/app/config/sudoers/aliases и опциональной очистки Keychain/Touch ID state.", ["uninstaller", "cleanup", "macos"]], + "libexec/lemana-vpn-dns-cleanup": ["lemana-vpn-dns-cleanup", "Узкий root-owned wrapper, который сбрасывает только корпоративные DNS и обновляет macOS resolver cache.", ["dns", "sudo-wrapper", "cleanup"]], + "templates/openconnect-lite-config.toml": ["openconnect-lite-config.toml", "Шаблон профиля openconnect-lite: gateway, credentials mode и Keycloak auto-fill selectors для username, password и TOTP.", ["configuration", "openconnect-lite", "autofill"]], + "tests/smoke.sh": ["smoke.sh", "Non-live smoke suite: shell syntax, installer/uninstaller checks, Swift wiring, SSO patch snippets, config selectors и regression assertions.", ["test", "smoke", "verification"]], +}; + +const functionSummaries = { + choose_credential_source_interactive: "Показывает интерактивный выбор credential source и нормализует ответ пользователя.", + choose_modules: "Определяет набор устанавливаемых модулей с учетом интерактивного режима и флагов installer.", + install_openconnect_lite: "Устанавливает или проверяет openconnect-lite runtime, необходимый для SAML SSO.", + install_cli: "Копирует основной CLI и uninstall helper в пользовательский bin.", + install_config: "Устанавливает шаблон openconnect-lite config и сохраняет локальную конфигурацию модулей.", + install_dns_cleanup: "Устанавливает root-owned DNS cleanup wrapper для безопасного сброса корпоративных DNS.", + install_sudoers: "Пишет ограниченные sudoers правила для openconnect и DNS cleanup wrapper.", + install_touchid_helper: "Собирает и устанавливает Touch ID helper для Bitwarden master password.", + install_menu_bar_app: "Собирает release-версию Swift menu bar app и устанавливает LemanaVPN.app.", + install_launch_agent: "Создает LaunchAgent для автозапуска menu bar app при логине.", + restart_running_menu_bar_app: "Перезапускает уже открытое menu bar приложение после обновления bundle.", + install_shell_aliases: "Добавляет idempotent zsh aliases для CLI режимов подключения и диагностики.", + maybe_login_bitwarden: "Проверяет Bitwarden session и при необходимости запускает login/unlock flow.", + restore_openconnect_lite_patch: "Возвращает backup исходника openconnect-lite, если runtime patch был установлен.", + remove_zshrc_block: "Удаляет idempotent блок Lemana VPN aliases из zshrc.", + remove_keychain_entries: "Опционально удаляет password, TOTP seed и username из macOS Keychain.", + _module_status_json: "Собирает машинно-читаемый JSON со статусом модулей, credentials, app, DNS и patches.", + _module_status_human: "Печатает человекочитаемую строку статуса модулей перед подключением.", + _check_status: "Проверяет PID/status file, running process, DNS target и возвращает disconnected/connected JSON.", + _patch_oc: "Применяет runtime patches к openconnect-lite для Keycloak autofill/manual/debug сценариев.", + _normalize_totp_secret: "Нормализует TOTP secret из raw secret или otpauth URI перед сохранением.", + _configure_keychain: "Интерактивно сохраняет LDAP password и TOTP seed в macOS Keychain.", + _ensure_keychain_credentials: "Проверяет наличие обязательных Keychain credentials перед auto/manual подключением.", + _bw_unlock: "Открывает Bitwarden session с учетом Touch ID helper и fallback prompts.", + _sync_bitwarden: "Синхронизирует LDAP password и TOTP seed из Bitwarden в macOS Keychain.", + _sync_credentials: "Выбирает credential source и запускает соответствующий sync/preflight путь.", + _dns_cleanup: "Вызывает narrow sudo wrapper для очистки VPN DNS при завершении сессии.", + _filter_output: "Фильтрует поток openconnect-lite, извлекает IP/expiry, пишет status JSON и события для app.", + refreshStatus: "Запускает CLI status JSON и обновляет module summary в menu bar app.", + connect: "Запускает CLI в JSON mode с выбранным режимом подключения и подписывается на stdout/stderr.", + disconnect: "Отправляет SIGINT текущему CLI process и останавливает reconnect state.", + parseEvent: "Декодирует JSON events из CLI и переводит их в Swift state updates.", + handleTermination: "Обрабатывает завершение CLI process и решает, нужен ли автоматический reconnect.", + startHealthCheck: "Периодически проверяет tunnel health и запускает reconnect при повторных failures.", +}; + +const swiftTypeSummaries = { + LemanaVPNApp: "Точка входа SwiftUI menu bar app, связывает VPNManager с MenuBarExtra.", + StatusBarLabel: "Компактный status bar label с иконкой и оставшимся временем сессии.", + VPNMenuView: "Основной menu UI с командами auto/manual/manual-full connect, disconnect, logs и quit.", + VPNEvent: "Decodable-модель JSON events, которые runtime CLI отправляет в Swift app.", + VPNStatusResponse: "Decodable envelope для ответа `vpn --status --json`.", + ModuleStatus: "Модель статуса модулей и вычисление summary/systemImage для menu bar app.", + VPNState: "Finite state model для disconnected/connecting/connected/reconnecting/error состояний.", + VPNLaunchMode: "Маппинг app launch modes на CLI flags `--auto`, `--manual`, `--manual-full`.", + VPNManager: "ObservableObject, который мостит Swift menu bar app с shell CLI, JSON events и health checks.", +}; + +function makeFileNode(file) { + const meta = fileMeta[file.path] || [nameOf(file.path), `Файл проекта ${file.path}.`, ["project-file", file.language, file.fileCategory]]; + const type = file.fileCategory === "docs" ? "document" : file.fileCategory === "config" ? "config" : "file"; + return { + id: fileNodeId(file), + type, + name: meta[0], + filePath: file.path, + summary: meta[1], + tags: meta[2], + complexity: complexity(file.sizeLines), + }; +} + +function shouldIncludeFunction(filePath, fn) { + if (fn.name === "autoFill" || fn.name === "vpn") return false; + const span = fn.endLine - fn.startLine + 1; + if (functionSummaries[fn.name]) return true; + if (fn.name === "usage" || fn.name === "main") return true; + if (/^(install_|remove_|restore_|choose_|_sync|_ensure|_configure|_patch|_check|_filter|_module|_dns)/.test(fn.name)) return true; + return span >= 18 && !/^log|color_line|die|run|need_cmd|has_tty|bool_word|_emit|_json_get|_can_prompt|_prompt_read/.test(fn.name); +} + +function makeFunctionNode(filePath, fn) { + const id = `function:${filePath}:${fn.name}`; + return { + id, + type: "function", + name: fn.name, + filePath, + lineRange: [fn.startLine, fn.endLine], + summary: functionSummaries[fn.name] || `Значимая функция ${fn.name} в ${filePath}.`, + tags: ["function", filePath.includes("test") ? "verification" : "runtime", fn.name.replace(/^_+/, "").replaceAll("_", "-")], + complexity: complexity(fn.endLine - fn.startLine + 1), + }; +} + +function swiftDecls(filePath) { + if (!filePath.endsWith(".swift")) return []; + const text = fs.readFileSync(path.join(root, filePath), "utf8"); + const lines = text.split(/\n/); + const decls = []; + lines.forEach((line, index) => { + const m = line.match(/^\s*(?:@main\s+)?(?:struct|class|enum)\s+([A-Za-z_][A-Za-z0-9_]*)/); + if (m && swiftTypeSummaries[m[1]]) { + decls.push({ name: m[1], startLine: index + 1 }); + } + }); + for (let i = 0; i < decls.length; i += 1) { + decls[i].endLine = (decls[i + 1]?.startLine || lines.length + 1) - 1; + } + return decls; +} + +function swiftMethods(filePath) { + if (!filePath.endsWith("VPNManager.swift")) return []; + const text = fs.readFileSync(path.join(root, filePath), "utf8"); + const lines = text.split(/\n/); + const wanted = new Set(["refreshStatus", "connect", "disconnect", "parseEvent", "handleTermination", "startHealthCheck"]); + const methods = []; + lines.forEach((line, index) => { + const m = line.match(/^\s*(?:private\s+)?func\s+([A-Za-z_][A-Za-z0-9_]*)\s*\(/); + if (m && wanted.has(m[1])) methods.push({ name: m[1], startLine: index + 1 }); + }); + for (let i = 0; i < methods.length; i += 1) { + methods[i].endLine = (methods[i + 1]?.startLine || Math.min(lines.length + 1, methods[i].startLine + 90)) - 1; + } + return methods; +} + +function graphForBatch(batch) { + const extract = JSON.parse(fs.readFileSync(path.join(tmp, `ua-file-extract-results-${batch.batchIndex}.json`), "utf8")); + const nodes = []; + const edges = []; + const resultByPath = new Map(extract.results.map((r) => [r.path, r])); + + for (const file of batch.files) { + const fileNode = makeFileNode(file); + nodes.push(fileNode); + const result = resultByPath.get(file.path); + + for (const fn of result?.functions || []) { + if (!shouldIncludeFunction(file.path, fn)) continue; + const node = makeFunctionNode(file.path, fn); + nodes.push(node); + edges.push(edge(fileNode.id, node.id, "contains", 1.0)); + } + + for (const decl of swiftDecls(file.path)) { + const node = { + id: `class:${file.path}:${decl.name}`, + type: "class", + name: decl.name, + filePath: file.path, + lineRange: [decl.startLine, decl.endLine], + summary: swiftTypeSummaries[decl.name], + tags: ["swift", "type", decl.name === "VPNManager" ? "state-manager" : "menu-bar-app"], + complexity: complexity(decl.endLine - decl.startLine + 1), + }; + nodes.push(node); + edges.push(edge(fileNode.id, node.id, "contains", 1.0)); + } + + for (const fn of swiftMethods(file.path)) { + const node = makeFunctionNode(file.path, fn); + nodes.push(node); + edges.push(edge(fileNode.id, node.id, "contains", 1.0)); + } + } + + return { nodes, edges }; +} + +for (const batch of batches) { + const graph = graphForBatch(batch); + fs.writeFileSync(path.join(inter, `batch-${batch.batchIndex}.json`), JSON.stringify(graph, null, 2)); +} + diff --git a/.understand-anything/tmp/ua-file-analyzer-input-1.json b/.understand-anything/tmp/ua-file-analyzer-input-1.json new file mode 100644 index 0000000..3d69106 --- /dev/null +++ b/.understand-anything/tmp/ua-file-analyzer-input-1.json @@ -0,0 +1,35 @@ +{ + "projectRoot": "/Users/dokril/git/home/lemana-vpn", + "batchFiles": [ + { + "path": "AGENTS.md", + "language": "markdown", + "sizeLines": 88, + "fileCategory": "docs" + }, + { + "path": "README.md", + "language": "markdown", + "sizeLines": 579, + "fileCategory": "docs" + }, + { + "path": "install.sh", + "language": "shell", + "sizeLines": 943, + "fileCategory": "script" + }, + { + "path": "uninstall.sh", + "language": "shell", + "sizeLines": 320, + "fileCategory": "script" + } + ], + "batchImportData": { + "AGENTS.md": [], + "README.md": [], + "install.sh": [], + "uninstall.sh": [] + } +} \ No newline at end of file diff --git a/.understand-anything/tmp/ua-file-analyzer-input-2.json b/.understand-anything/tmp/ua-file-analyzer-input-2.json new file mode 100644 index 0000000..5f24e5a --- /dev/null +++ b/.understand-anything/tmp/ua-file-analyzer-input-2.json @@ -0,0 +1,98 @@ +{ + "projectRoot": "/Users/dokril/git/home/lemana-vpn", + "batchFiles": [ + { + "path": ".agents/skills/lemana-vpn-operations/SKILL.md", + "language": "markdown", + "sizeLines": 70, + "fileCategory": "docs" + }, + { + "path": ".agents/skills/lemana-vpn-sso-autofill/SKILL.md", + "language": "markdown", + "sizeLines": 80, + "fileCategory": "docs" + }, + { + "path": ".agents/skills/lemana-vpn-testing/SKILL.md", + "language": "markdown", + "sizeLines": 101, + "fileCategory": "docs" + }, + { + "path": "app/Package.swift", + "language": "swift", + "sizeLines": 14, + "fileCategory": "code" + }, + { + "path": "app/Sources/LemanaVPN/LemanaVPNApp.swift", + "language": "swift", + "sizeLines": 190, + "fileCategory": "code" + }, + { + "path": "app/Sources/LemanaVPN/VPNManager.swift", + "language": "swift", + "sizeLines": 697, + "fileCategory": "code" + }, + { + "path": "bin/vpn-lemanapro.sh", + "language": "shell", + "sizeLines": 1090, + "fileCategory": "script" + }, + { + "path": "docs/goals/manual-first-auth-flow/GOAL.md", + "language": "markdown", + "sizeLines": 12, + "fileCategory": "docs" + }, + { + "path": "docs/goals/manual-first-auth-flow/PLAN.md", + "language": "markdown", + "sizeLines": 251, + "fileCategory": "docs" + }, + { + "path": "docs/superpowers/plans/2026-05-24-keychain-totp-provider.md", + "language": "markdown", + "sizeLines": 935, + "fileCategory": "docs" + }, + { + "path": "libexec/lemana-vpn-dns-cleanup", + "language": "unknown", + "sizeLines": 23, + "fileCategory": "code" + }, + { + "path": "templates/openconnect-lite-config.toml", + "language": "toml", + "sizeLines": 32, + "fileCategory": "config" + }, + { + "path": "tests/smoke.sh", + "language": "shell", + "sizeLines": 438, + "fileCategory": "script" + } + ], + "batchImportData": { + ".agents/skills/lemana-vpn-operations/SKILL.md": [], + ".agents/skills/lemana-vpn-sso-autofill/SKILL.md": [], + ".agents/skills/lemana-vpn-testing/SKILL.md": [], + "app/Package.swift": [], + "app/Sources/LemanaVPN/LemanaVPNApp.swift": [], + "app/Sources/LemanaVPN/VPNManager.swift": [], + "bin/vpn-lemanapro.sh": [], + "docs/goals/manual-first-auth-flow/GOAL.md": [], + "docs/goals/manual-first-auth-flow/PLAN.md": [], + "docs/superpowers/plans/2026-05-24-keychain-totp-provider.md": [], + "libexec/lemana-vpn-dns-cleanup": [], + "templates/openconnect-lite-config.toml": [], + "tests/smoke.sh": [] + } +} \ No newline at end of file diff --git a/.understand-anything/tmp/ua-file-extract-results-1.json b/.understand-anything/tmp/ua-file-extract-results-1.json new file mode 100644 index 0000000..7c23877 --- /dev/null +++ b/.understand-anything/tmp/ua-file-extract-results-1.json @@ -0,0 +1,540 @@ +{ + "scriptCompleted": true, + "filesAnalyzed": 4, + "filesSkipped": [], + "results": [ + { + "path": "AGENTS.md", + "language": "markdown", + "fileCategory": "docs", + "totalLines": 88, + "nonEmptyLines": 61, + "sections": [ + { + "heading": "Lemana VPN Agent Instructions", + "level": 1, + "line": 1 + }, + { + "heading": "Scope", + "level": 2, + "line": 3 + }, + { + "heading": "System Model", + "level": 2, + "line": 13 + }, + { + "heading": "Live VPN Safety", + "level": 2, + "line": 22 + }, + { + "heading": "Testing", + "level": 2, + "line": 44 + }, + { + "heading": "Answer Contract", + "level": 2, + "line": 60 + } + ], + "metrics": { + "importCount": 0, + "exportCount": 0, + "functionCount": 0, + "classCount": 0, + "sectionCount": 6 + } + }, + { + "path": "README.md", + "language": "markdown", + "fileCategory": "docs", + "totalLines": 579, + "nonEmptyLines": 405, + "sections": [ + { + "heading": "Lemana VPN", + "level": 1, + "line": 1 + }, + { + "heading": "Быстрая установка", + "level": 2, + "line": 19 + }, + { + "heading": "Интерактивная установка", + "level": 2, + "line": 34 + }, + { + "heading": "Что ставится", + "level": 2, + "line": 94 + }, + { + "heading": "Статус модулей", + "level": 2, + "line": 111 + }, + { + "heading": "Интерактивная установка", + "level": 2, + "line": 148 + }, + { + "heading": "Логи установщика и удаления", + "level": 2, + "line": 183 + }, + { + "heading": "Модули", + "level": 2, + "line": 201 + }, + { + "heading": "Core", + "level": 3, + "line": 203 + }, + { + "heading": "Как работают credential sources", + "level": 3, + "line": 214 + }, + { + "heading": "Bitwarden", + "level": 3, + "line": 235 + }, + { + "heading": "Если Bitwarden нет", + "level": 3, + "line": 259 + }, + { + "heading": "Touch ID", + "level": 3, + "line": 302 + }, + { + "heading": "Swift Menu Bar app", + "level": 3, + "line": 314 + }, + { + "heading": "Использование", + "level": 2, + "line": 352 + }, + { + "heading": "Настройка", + "level": 2, + "line": 385 + }, + { + "heading": "Bitwarden item", + "level": 2, + "line": 415 + }, + { + "heading": "Почему DNS wrapper, а не wildcard sudoers", + "level": 2, + "line": 426 + }, + { + "heading": "Runtime-патчи openconnect-lite", + "level": 2, + "line": 432 + }, + { + "heading": "Диагностика", + "level": 2, + "line": 460 + }, + { + "heading": "Удаление", + "level": 2, + "line": 528 + } + ], + "metrics": { + "importCount": 0, + "exportCount": 0, + "functionCount": 0, + "classCount": 0, + "sectionCount": 21 + } + }, + { + "path": "install.sh", + "language": "shell", + "fileCategory": "script", + "totalLines": 943, + "nonEmptyLines": 824, + "functions": [ + { + "name": "usage", + "startLine": 72, + "endLine": 106, + "params": [] + }, + { + "name": "log", + "startLine": 220, + "endLine": 222, + "params": [] + }, + { + "name": "color_line", + "startLine": 224, + "endLine": 228, + "params": [] + }, + { + "name": "log_step", + "startLine": 230, + "endLine": 232, + "params": [] + }, + { + "name": "log_info", + "startLine": 234, + "endLine": 236, + "params": [] + }, + { + "name": "log_detail", + "startLine": 238, + "endLine": 240, + "params": [] + }, + { + "name": "log_ok", + "startLine": 242, + "endLine": 244, + "params": [] + }, + { + "name": "log_warn", + "startLine": 246, + "endLine": 248, + "params": [] + }, + { + "name": "log_skip", + "startLine": 250, + "endLine": 252, + "params": [] + }, + { + "name": "die", + "startLine": 254, + "endLine": 257, + "params": [] + }, + { + "name": "run", + "startLine": 259, + "endLine": 269, + "params": [] + }, + { + "name": "need_cmd", + "startLine": 271, + "endLine": 273, + "params": [] + }, + { + "name": "has_tty", + "startLine": 275, + "endLine": 277, + "params": [] + }, + { + "name": "interactive_enabled", + "startLine": 279, + "endLine": 286, + "params": [] + }, + { + "name": "prompt_printf", + "startLine": 288, + "endLine": 294, + "params": [] + }, + { + "name": "prompt_read_answer", + "startLine": 296, + "endLine": 302, + "params": [] + }, + { + "name": "yes_no", + "startLine": 304, + "endLine": 330, + "params": [] + }, + { + "name": "choose_credential_source_interactive", + "startLine": 332, + "endLine": 382, + "params": [] + }, + { + "name": "bool_word", + "startLine": 384, + "endLine": 390, + "params": [] + }, + { + "name": "keychain_has", + "startLine": 392, + "endLine": 394, + "params": [] + }, + { + "name": "zsh_aliases_installed", + "startLine": 396, + "endLine": 398, + "params": [] + }, + { + "name": "print_detected_state", + "startLine": 400, + "endLine": 417, + "params": [] + }, + { + "name": "choose_modules", + "startLine": 419, + "endLine": 514, + "params": [] + }, + { + "name": "script_dir", + "startLine": 516, + "endLine": 521, + "params": [] + }, + { + "name": "download_file", + "startLine": 523, + "endLine": 540, + "params": [] + }, + { + "name": "write_file", + "startLine": 542, + "endLine": 550, + "params": [] + }, + { + "name": "install_homebrew_packages", + "startLine": 552, + "endLine": 578, + "params": [] + }, + { + "name": "install_openconnect_lite", + "startLine": 580, + "endLine": 597, + "params": [] + }, + { + "name": "install_cli", + "startLine": 599, + "endLine": 613, + "params": [] + }, + { + "name": "install_config", + "startLine": 615, + "endLine": 639, + "params": [] + }, + { + "name": "install_dns_cleanup", + "startLine": 641, + "endLine": 654, + "params": [] + }, + { + "name": "install_sudoers", + "startLine": 656, + "endLine": 683, + "params": [] + }, + { + "name": "install_touchid_helper", + "startLine": 685, + "endLine": 708, + "params": [] + }, + { + "name": "install_menu_bar_app", + "startLine": 710, + "endLine": 769, + "params": [] + }, + { + "name": "install_launch_agent", + "startLine": 771, + "endLine": 811, + "params": [] + }, + { + "name": "restart_running_menu_bar_app", + "startLine": 813, + "endLine": 831, + "params": [] + }, + { + "name": "install_shell_aliases", + "startLine": 833, + "endLine": 877, + "params": [] + }, + { + "name": "vpn", + "startLine": 854, + "endLine": 854, + "params": [] + }, + { + "name": "maybe_login_bitwarden", + "startLine": 879, + "endLine": 900, + "params": [] + }, + { + "name": "main", + "startLine": 902, + "endLine": 941, + "params": [] + } + ], + "metrics": { + "importCount": 0, + "exportCount": 0, + "functionCount": 40, + "classCount": 0 + } + }, + { + "path": "uninstall.sh", + "language": "shell", + "fileCategory": "script", + "totalLines": 320, + "nonEmptyLines": 279, + "functions": [ + { + "name": "usage", + "startLine": 62, + "endLine": 79, + "params": [] + }, + { + "name": "log", + "startLine": 102, + "endLine": 104, + "params": [] + }, + { + "name": "color_line", + "startLine": 106, + "endLine": 110, + "params": [] + }, + { + "name": "log_step", + "startLine": 112, + "endLine": 114, + "params": [] + }, + { + "name": "log_info", + "startLine": 116, + "endLine": 118, + "params": [] + }, + { + "name": "log_detail", + "startLine": 120, + "endLine": 122, + "params": [] + }, + { + "name": "log_ok", + "startLine": 124, + "endLine": 126, + "params": [] + }, + { + "name": "log_warn", + "startLine": 128, + "endLine": 130, + "params": [] + }, + { + "name": "log_skip", + "startLine": 132, + "endLine": 134, + "params": [] + }, + { + "name": "die", + "startLine": 136, + "endLine": 139, + "params": [] + }, + { + "name": "run", + "startLine": 141, + "endLine": 151, + "params": [] + }, + { + "name": "find_webengine_process", + "startLine": 153, + "endLine": 159, + "params": [] + }, + { + "name": "restore_openconnect_lite_patch", + "startLine": 161, + "endLine": 181, + "params": [] + }, + { + "name": "remove_zshrc_block", + "startLine": 183, + "endLine": 204, + "params": [] + }, + { + "name": "remove_keychain_entries", + "startLine": 206, + "endLine": 234, + "params": [] + }, + { + "name": "main", + "startLine": 236, + "endLine": 318, + "params": [] + } + ], + "metrics": { + "importCount": 0, + "exportCount": 0, + "functionCount": 16, + "classCount": 0 + } + } + ] +} \ No newline at end of file diff --git a/.understand-anything/tmp/ua-file-extract-results-2.json b/.understand-anything/tmp/ua-file-extract-results-2.json new file mode 100644 index 0000000..f22d72a --- /dev/null +++ b/.understand-anything/tmp/ua-file-extract-results-2.json @@ -0,0 +1,727 @@ +{ + "scriptCompleted": true, + "filesAnalyzed": 13, + "filesSkipped": [], + "results": [ + { + "path": ".agents/skills/lemana-vpn-operations/SKILL.md", + "language": "markdown", + "fileCategory": "docs", + "totalLines": 70, + "nonEmptyLines": 48, + "sections": [ + { + "heading": "Lemana VPN Operations", + "level": 1, + "line": 6 + }, + { + "heading": "Overview", + "level": 2, + "line": 8 + }, + { + "heading": "System Map", + "level": 2, + "line": 12 + }, + { + "heading": "Live Connection Rules", + "level": 2, + "line": 20 + }, + { + "heading": "Update Commands", + "level": 2, + "line": 38 + }, + { + "heading": "Answer Format", + "level": 2, + "line": 60 + }, + { + "heading": "Common Mistakes", + "level": 2, + "line": 64 + } + ], + "metrics": { + "importCount": 0, + "exportCount": 0, + "functionCount": 0, + "classCount": 0, + "sectionCount": 7 + } + }, + { + "path": ".agents/skills/lemana-vpn-sso-autofill/SKILL.md", + "language": "markdown", + "fileCategory": "docs", + "totalLines": 80, + "nonEmptyLines": 54, + "sections": [ + { + "heading": "Lemana VPN SSO And Autofill", + "level": 1, + "line": 6 + }, + { + "heading": "Overview", + "level": 2, + "line": 8 + }, + { + "heading": "Credential Flow", + "level": 2, + "line": 12 + }, + { + "heading": "Runtime Patches", + "level": 2, + "line": 29 + }, + { + "heading": "Modes", + "level": 2, + "line": 47 + }, + { + "heading": "Testing", + "level": 2, + "line": 56 + }, + { + "heading": "Troubleshooting Signals", + "level": 2, + "line": 76 + } + ], + "metrics": { + "importCount": 0, + "exportCount": 0, + "functionCount": 0, + "classCount": 0, + "sectionCount": 7 + } + }, + { + "path": ".agents/skills/lemana-vpn-testing/SKILL.md", + "language": "markdown", + "fileCategory": "docs", + "totalLines": 101, + "nonEmptyLines": 68, + "sections": [ + { + "heading": "Lemana VPN Testing", + "level": 1, + "line": 6 + }, + { + "heading": "Overview", + "level": 2, + "line": 8 + }, + { + "heading": "Non-Live Verification", + "level": 2, + "line": 12 + }, + { + "heading": "Status Checks", + "level": 2, + "line": 28 + }, + { + "heading": "Live VPN Checks", + "level": 2, + "line": 46 + }, + { + "heading": "Installer/Uninstaller Checks", + "level": 2, + "line": 83 + }, + { + "heading": "Final Report", + "level": 2, + "line": 94 + } + ], + "metrics": { + "importCount": 0, + "exportCount": 0, + "functionCount": 0, + "classCount": 0, + "sectionCount": 7 + } + }, + { + "path": "app/Package.swift", + "language": "swift", + "fileCategory": "code", + "totalLines": 14, + "nonEmptyLines": 12, + "metrics": {} + }, + { + "path": "app/Sources/LemanaVPN/LemanaVPNApp.swift", + "language": "swift", + "fileCategory": "code", + "totalLines": 190, + "nonEmptyLines": 174, + "metrics": {} + }, + { + "path": "app/Sources/LemanaVPN/VPNManager.swift", + "language": "swift", + "fileCategory": "code", + "totalLines": 697, + "nonEmptyLines": 618, + "metrics": {} + }, + { + "path": "bin/vpn-lemanapro.sh", + "language": "shell", + "fileCategory": "script", + "totalLines": 1090, + "nonEmptyLines": 954, + "functions": [ + { + "name": "_emit", + "startLine": 119, + "endLine": 126, + "params": [] + }, + { + "name": "_write_status", + "startLine": 128, + "endLine": 131, + "params": [] + }, + { + "name": "_clear_status", + "startLine": 133, + "endLine": 135, + "params": [] + }, + { + "name": "_prepare_connection_log", + "startLine": 137, + "endLine": 144, + "params": [] + }, + { + "name": "_log_connection_line", + "startLine": 146, + "endLine": 148, + "params": [] + }, + { + "name": "_show_connection_log_tail", + "startLine": 150, + "endLine": 154, + "params": [] + }, + { + "name": "_start_connect_progress", + "startLine": 158, + "endLine": 166, + "params": [] + }, + { + "name": "_stop_connect_progress", + "startLine": 168, + "endLine": 174, + "params": [] + }, + { + "name": "_json_get", + "startLine": 176, + "endLine": 179, + "params": [] + }, + { + "name": "_find_webengine_process", + "startLine": 181, + "endLine": 187, + "params": [] + }, + { + "name": "_find_authenticator", + "startLine": 189, + "endLine": 195, + "params": [] + }, + { + "name": "_module_bool", + "startLine": 197, + "endLine": 203, + "params": [] + }, + { + "name": "_patches_active", + "startLine": 205, + "endLine": 219, + "params": [] + }, + { + "name": "_keychain_has", + "startLine": 221, + "endLine": 223, + "params": [] + }, + { + "name": "_module_status_json", + "startLine": 225, + "endLine": 264, + "params": [] + }, + { + "name": "_module_human_part", + "startLine": 266, + "endLine": 275, + "params": [] + }, + { + "name": "_module_status_human", + "startLine": 277, + "endLine": 311, + "params": [] + }, + { + "name": "_check_status", + "startLine": 313, + "endLine": 375, + "params": [] + }, + { + "name": "_patch_oc", + "startLine": 377, + "endLine": 604, + "params": [] + }, + { + "name": "autoFill", + "startLine": 470, + "endLine": 473, + "params": [] + }, + { + "name": "autoFill", + "startLine": 488, + "endLine": 497, + "params": [] + }, + { + "name": "_store_keychain", + "startLine": 606, + "endLine": 621, + "params": [] + }, + { + "name": "_normalize_totp_secret", + "startLine": 623, + "endLine": 649, + "params": [] + }, + { + "name": "_can_prompt", + "startLine": 651, + "endLine": 653, + "params": [] + }, + { + "name": "_prompt_read", + "startLine": 655, + "endLine": 673, + "params": [] + }, + { + "name": "_persist_username", + "startLine": 675, + "endLine": 718, + "params": [] + }, + { + "name": "_ensure_username", + "startLine": 720, + "endLine": 745, + "params": [] + }, + { + "name": "_configure_keychain", + "startLine": 747, + "endLine": 785, + "params": [] + }, + { + "name": "_ensure_keychain_credentials", + "startLine": 787, + "endLine": 814, + "params": [] + }, + { + "name": "_bw_cache_session", + "startLine": 816, + "endLine": 821, + "params": [] + }, + { + "name": "_bw_unlock", + "startLine": 823, + "endLine": 889, + "params": [] + }, + { + "name": "_sync_bitwarden", + "startLine": 891, + "endLine": 925, + "params": [] + }, + { + "name": "_sync_credentials", + "startLine": 927, + "endLine": 936, + "params": [] + }, + { + "name": "_dns_cleanup", + "startLine": 938, + "endLine": 954, + "params": [] + }, + { + "name": "_filter_output", + "startLine": 956, + "endLine": 1006, + "params": [] + } + ], + "metrics": { + "importCount": 0, + "exportCount": 0, + "functionCount": 35, + "classCount": 0 + } + }, + { + "path": "docs/goals/manual-first-auth-flow/GOAL.md", + "language": "markdown", + "fileCategory": "docs", + "totalLines": 13, + "nonEmptyLines": 11, + "sections": [ + { + "heading": "Goal: Manual-First Auth Flow", + "level": 1, + "line": 1 + } + ], + "metrics": { + "importCount": 0, + "exportCount": 0, + "functionCount": 0, + "classCount": 0, + "sectionCount": 1 + } + }, + { + "path": "docs/goals/manual-first-auth-flow/PLAN.md", + "language": "markdown", + "fileCategory": "docs", + "totalLines": 440, + "nonEmptyLines": 318, + "sections": [ + { + "heading": "Manual-First Auth Flow Implementation Plan", + "level": 1, + "line": 1 + }, + { + "heading": "Короткий План", + "level": 2, + "line": 18 + }, + { + "heading": "Architecture Slice", + "level": 2, + "line": 40 + }, + { + "heading": "Пользовательский Процесс Установки", + "level": 2, + "line": 120 + }, + { + "heading": "Первый Экран", + "level": 3, + "line": 124 + }, + { + "heading": "Шаг 1: Что Будет Установлено", + "level": 3, + "line": 148 + }, + { + "heading": "Шаг 2: Базовый Режим Входа", + "level": 3, + "line": 162 + }, + { + "heading": "Шаг 3: Автоматический Вход Только Для Продвинутого Пути", + "level": 3, + "line": 187 + }, + { + "heading": "Шаг 4: Bitwarden Как Дополнительная Функция", + "level": 3, + "line": 214 + }, + { + "heading": "Шаг 5: Touch ID Только После Bitwarden", + "level": 3, + "line": 234 + }, + { + "heading": "Шаг 6: Понятный Прогресс Установки", + "level": 3, + "line": 246 + }, + { + "heading": "Финальный Экран", + "level": 3, + "line": 265 + }, + { + "heading": "Ошибки И Подсказки", + "level": 3, + "line": 292 + }, + { + "heading": "Acceptance Evidence Для Installer UX", + "level": 3, + "line": 301 + }, + { + "heading": "Дополнительные Функции", + "level": 2, + "line": 308 + }, + { + "heading": "Tasks", + "level": 2, + "line": 345 + }, + { + "heading": "Task 1: Rewrite user-facing contract and docs", + "level": 3, + "line": 347 + }, + { + "heading": "Task 2: Change installer defaults and capability selection", + "level": 3, + "line": 356 + }, + { + "heading": "Task 3: Refactor CLI auth profiles and partial Keychain support", + "level": 3, + "line": 365 + }, + { + "heading": "Task 4: Preserve automatic compatibility without keeping it dominant", + "level": 3, + "line": 374 + }, + { + "heading": "Task 5: Update Swift menu-bar app surface", + "level": 3, + "line": 383 + }, + { + "heading": "Task 6: Expand smoke coverage", + "level": 3, + "line": 392 + }, + { + "heading": "Task 7: Final verification and local update notes", + "level": 3, + "line": 401 + }, + { + "heading": "Non-Goals", + "level": 2, + "line": 417 + }, + { + "heading": "Risks", + "level": 2, + "line": 425 + }, + { + "heading": "Execution Order", + "level": 2, + "line": 432 + } + ], + "metrics": { + "importCount": 0, + "exportCount": 0, + "functionCount": 0, + "classCount": 0, + "sectionCount": 26 + } + }, + { + "path": "docs/superpowers/plans/2026-05-24-keychain-totp-provider.md", + "language": "markdown", + "fileCategory": "docs", + "totalLines": 935, + "nonEmptyLines": 656, + "sections": [ + { + "heading": "Keychain TOTP Provider Implementation Plan", + "level": 1, + "line": 1 + }, + { + "heading": "File Structure", + "level": 2, + "line": 13 + }, + { + "heading": "Task 1: Add Smoke Tests For Credential Source Separation", + "level": 3, + "line": 39 + }, + { + "heading": "Task 2: Add Runtime Credential Source And TOTP Normalization", + "level": 3, + "line": 135 + }, + { + "heading": "Task 3: Split Bitwarden Sync From Keychain Source Flow", + "level": 3, + "line": 325 + }, + { + "heading": "Task 4: Update Installer Flags And Config", + "level": 3, + "line": 457 + }, + { + "heading": "Task 5: Update Swift Menu-Bar Status Decoding", + "level": 3, + "line": 639 + }, + { + "heading": "Task 6: Update Documentation", + "level": 3, + "line": 736 + }, + { + "heading": "Task 7: Full Verification", + "level": 3, + "line": 799 + }, + { + "heading": "Self-Review", + "level": 2, + "line": 931 + } + ], + "metrics": { + "importCount": 0, + "exportCount": 0, + "functionCount": 0, + "classCount": 0, + "sectionCount": 10 + } + }, + { + "path": "libexec/lemana-vpn-dns-cleanup", + "language": "unknown", + "fileCategory": "code", + "totalLines": 23, + "nonEmptyLines": 17, + "metrics": {} + }, + { + "path": "templates/openconnect-lite-config.toml", + "language": "toml", + "fileCategory": "config", + "totalLines": 32, + "nonEmptyLines": 23, + "sections": [ + { + "heading": "default_profile", + "level": 1, + "line": 3 + }, + { + "heading": "credentials", + "level": 1, + "line": 8 + }, + { + "heading": "auto_fill_rules", + "level": 1, + "line": 11 + }, + { + "heading": "[[auto_fill_rules.\"https://employee.auth.lemanapro.ru/*\"]]", + "level": 5, + "line": 13 + }, + { + "heading": "[[auto_fill_rules.\"https://employee.auth.lemanapro.ru/*\"]]", + "level": 5, + "line": 17 + }, + { + "heading": "[[auto_fill_rules.\"https://employee.auth.lemanapro.ru/*\"]]", + "level": 5, + "line": 21 + }, + { + "heading": "[[auto_fill_rules.\"https://employee.auth.lemanapro.ru/*\"]]", + "level": 5, + "line": 25 + }, + { + "heading": "[[auto_fill_rules.\"https://employee.auth.lemanapro.ru/*\"]]", + "level": 5, + "line": 29 + } + ], + "metrics": { + "importCount": 0, + "exportCount": 0, + "functionCount": 0, + "classCount": 0, + "sectionCount": 8 + } + }, + { + "path": "tests/smoke.sh", + "language": "shell", + "fileCategory": "script", + "totalLines": 438, + "nonEmptyLines": 392, + "functions": [ + { + "name": "autoFill", + "startLine": 113, + "endLine": 116, + "params": [] + } + ], + "metrics": { + "importCount": 0, + "exportCount": 0, + "functionCount": 1, + "classCount": 0 + } + } + ] +} \ No newline at end of file diff --git a/docs/goals/manual-first-auth-flow/GOAL.md b/docs/goals/manual-first-auth-flow/GOAL.md new file mode 100644 index 0000000..d94690e --- /dev/null +++ b/docs/goals/manual-first-auth-flow/GOAL.md @@ -0,0 +1,13 @@ +# Goal: Manual-First Auth Flow + +Use Krypton Execution to execute `docs/goals/manual-first-auth-flow/PLAN.md`. + +Core rules: +- Treat PLAN.md as the source plan. +- Preserve intent, ownership, contract, cutover, evidence, and kill criteria. +- Make fresh install and default `vpn` / app connect manual-first. +- Keep the installer understandable for a non-IT user: plain wording, manual mode on Enter, optional conveniences explained as choices. +- Keep Bitwarden, Touch ID, saved TOTP, autosubmit, and headless mode as explicit opt-in features. +- Do not add a new dominant path without deleting, redirecting, demoting, or shimming the displaced Bitwarden/auto-first path. +- Capture acceptance evidence from a clean temporary HOME and the menu-bar app contract. +- Say "implemented but unproven" if that evidence cannot be captured. diff --git a/docs/goals/manual-first-auth-flow/PLAN.md b/docs/goals/manual-first-auth-flow/PLAN.md new file mode 100644 index 0000000..f11773e --- /dev/null +++ b/docs/goals/manual-first-auth-flow/PLAN.md @@ -0,0 +1,440 @@ +# Manual-First Auth Flow Implementation Plan + +**Intent:** Перевернуть Lemana VPN из auto/Bitwarden-first в manual-first: новая установка и действие по умолчанию открывают видимый SSO-браузер без сохранённых секретов, а удобства включаются отдельными функциями. +**Current Behavior:** Fresh install по умолчанию выбирает `credential_source=bitwarden`, ставит Bitwarden/Touch ID, `vpn` запускает hidden browser с autofill+submit, а `manual-full` существует как отдельный диагностический режим. +**Expected Outcome:** Базовый путь не требует Bitwarden, Keychain, TOTP seed или автоотправки. Пользователь может поэтапно включить сохранение LDAP login/password в macOS Keychain, затем TOTP seed/autofill, затем Bitwarden sync, Touch ID, autosubmit/headless. +**Target-Perspective Output:** Новый пользователь запускает installer, ничего специально не выбирает, нажимает `vpn` или основную кнопку в `LemanaVPN.app`, видит Keycloak форму и вводит LDAP, пароль и одноразовый код руками. Если он включил сохранение LDAP в Keychain, username/password подставляются, а одноразовый код он вводит руками. +**Truth Owner:** `bin/vpn-lemanapro.sh` владеет runtime-решением режима; `~/.config/lemana-vpn/env` хранит выбранные capabilities; macOS Keychain хранит только секреты; Swift app только вызывает CLI и читает JSON. +**Contract Boundary:** Installer пишет env/config, CLI читает env/flags/Keychain и отдаёт JSON Lines/status JSON, Swift app потребляет только этот CLI-контракт. `openconnect-lite` остаётся SSO/VPN engine. +**Cutover:** Fresh installs become manual-first. Existing configs with `LEMANA_VPN_CREDENTIAL_SOURCE=bitwarden` keep working, but docs/UI stop presenting Bitwarden as default. Legacy aliases `vpn-auto`, `vpn-manual`, `vpn-manual-full` stay as compatibility shims. +**Displaced Path:** The displaced dominant path is `Bitwarden + Touch ID + hidden autofill + autosubmit by default`. It becomes an explicit opt-in feature set, not the default route. +**Value Density:** First slice removes the biggest onboarding failure: users without Bitwarden can connect manually immediately, then add only the conveniences they need. +**Acceptance Evidence:** In a clean temporary HOME, `sh install.sh --dry-run --non-interactive` reports manual/default auth with Bitwarden and Touch ID off; the interactive installer copy reads like a step-by-step setup for a non-IT user; `bash bin/vpn-lemanapro.sh --json` without credentials reaches a visible browser launch without `username_required` or `keychain_required`; a Keychain-login-only fixture fills username/password but leaves OTP manual and submit disabled; the Swift app primary action maps to the manual-first path. +**Evidence Lane:** Non-live smoke ladder first: `sh -n`, `bash -n`, `tests/smoke.sh`, `swift build`, `git diff --check`. Live VPN is optional after status check and should start with visible manual mode only. +**Kill Criteria:** No fresh-install default may select Bitwarden, require a TOTP seed, autosubmit, or hide the browser. No docs may state Bitwarden is enabled by default. No default CLI/app path may require saved credentials before opening manual SSO. +**Architecture Slice:** Installer defaults + CLI auth profile/capability model + Swift launch mode labels + README/smoke coverage. +**Plan Review Gate:** Requires PRE review before execution. + +## Короткий План + +🎯 Цель: fresh install, `vpn` и основная кнопка приложения стартуют с полностью ручного SSO; сохранение LDAP, TOTP, Bitwarden, autosubmit и headless включаются отдельно. + +📦 Репозитории: только `/Users/dokril/git/home/lemana-vpn`. + +🧩 Модули: `install.sh`, `bin/vpn-lemanapro.sh`, `app/Sources/LemanaVPN`, `tests/smoke.sh`, `README.md`; `templates/openconnect-lite-config.toml` только если login-only autofill потребует правки правил. + +🛠 Основные изменения: +- Переключить fresh defaults с Bitwarden/auto-first на manual-first. +- Ввести явный auth profile/capability contract: manual, saved-login, saved-totp, auto. +- Разрешить Keychain хранить только LDAP username/password без обязательного TOTP seed. +- Оставить Bitwarden, Touch ID, autosubmit/headless как opt-in функции. +- Сделать Swift app CLI-wrapper поверх тех же режимов, с manual-first primary action. +- Зафиксировать всё в smoke-тестах и README. + +✅ Проверка: `sh -n install.sh`, `sh -n uninstall.sh`, `bash -n bin/vpn-lemanapro.sh`, `sh -n tests/smoke.sh`, `tests/smoke.sh`, `swift build -c release --package-path app`, `git diff --check`. + +⚠️ Риски: старые пользователи могут ожидать auto-connect на `vpn`; partial Keychain без TOTP надо проверить fake launch тестом и, при необходимости, видимым manual smoke. + +➡️ Следующий шаг: начать с `install.sh` и зафиксировать новый env/flag contract для manual-first defaults. + +## Architecture Slice + +Files to create: + +- None required for runtime. This goal package is the planning artifact. + +Files to modify: + +- `install.sh` - defaults, wizard wording, flags, env rendering, dry-run output. +- `bin/vpn-lemanapro.sh` - auth profile/capability model, default mode, partial Keychain support, JSON events. +- `app/Sources/LemanaVPN/VPNManager.swift` - launch mode enum/default, JSON handling if new events are added. +- `app/Sources/LemanaVPN/LemanaVPNApp.swift` - menu labels/order so manual-first is primary and auto is secondary. +- `tests/smoke.sh` - regression coverage for fresh defaults, manual launch without credentials, partial Keychain, app wiring. +- `README.md` - user-facing feature matrix and migration/update instructions. +- `templates/openconnect-lite-config.toml` only if login-only filling needs rule adjustments; otherwise avoid. + +Files to avoid: + +- `libexec/lemana-vpn-dns-cleanup` unless DNS behavior breaks. +- `uninstall.sh` except if new Keychain account names require cleanup. +- Direct edits inside installed `openconnect-lite`; use the existing runtime patch path from `bin/vpn-lemanapro.sh`. + +Source of truth: + +- Repo source for behavior. +- Installed env file for user-selected capabilities. +- Keychain for LDAP password and optional TOTP seed. + +Read path: + +- `install.sh` writes `~/.config/lemana-vpn/env`. +- `bin/vpn-lemanapro.sh` sources env, applies CLI flag overrides, checks Keychain capability presence, then launches `openconnect-lite`. +- `LemanaVPN.app` calls `~/bin/vpn-lemanapro.sh --json `. + +Write path: + +- `vpn --configure-login` or equivalent writes username/password only. +- `vpn --configure-keychain` remains the full saved-credentials path and writes password plus permanent TOTP seed. +- Bitwarden sync, when enabled, writes password plus optional TOTP seed into Keychain. + +Contract boundary: + +- CLI flags and JSON events are the public contract for the app and tests. +- Suggested new runtime contract: + - `auth_profile=manual`: visible browser, no preflight, no autofill, no submit. + - `auth_profile=saved-login`: visible browser, username/password autofill, OTP manual, no submit. + - `auth_profile=saved-totp`: visible browser, username/password/TOTP autofill, no submit. + - `auth_profile=auto`: hidden or visible automatic mode, full autofill and submit; explicit only. + - `credential_source=none|keychain|bitwarden`: where saved values come from, separate from whether submit/headless are enabled. + +Integration points: + +- macOS Keychain service `openconnect-lite`. +- Optional Bitwarden CLI item `LM LDAP`. +- `openconnect-lite` config autofill rules. +- Swift app menu and reconnect behavior. +- Existing aliases from shell install block. + +Migration/cutover: + +- Fresh installs write `credential_source=none` and `auth_profile=manual`. +- Existing `credential_source=bitwarden` installs continue as saved-totp/auto-capable, but `vpn` default should still prefer manual-visible unless the user explicitly chooses auto. +- `vpn-auto` remains the explicit old behavior. +- `--manual-full` remains compatibility alias for the new default manual profile. + +Displaced path: + +- Installer Enter=Bitwarden. +- CLI `CONNECT_MODE=auto`. +- Full password+TOTP seed as mandatory preflight for non-`manual-full`. +- App primary/default connect action pointing at `.auto`. + +Acceptance evidence gate: + +- A clean temp HOME proves fresh defaults. +- Captured interactive installer output proves that the default path is understandable without knowing Bitwarden, Keychain, TOTP seed, or `credential_source`. +- Fake `openconnect-lite` launch capture proves display/autofill/click env for each profile. +- App source/smoke proves primary UI wiring. +- Installed-bundle verification is required only when refreshing the local app after implementation. + +## Пользовательский Процесс Установки + +Цель этого сценария: человек, далёкий от IT, должен понимать, что сейчас произойдёт, что от него требуется, и что можно пропустить. Installer не должен начинать с технического выбора credential source. + +### Первый Экран + +Текст должен быть коротким: + +```text +Lemana VPN установит приложение для подключения к корпоративному VPN. + +По умолчанию ничего не сохраняется: при подключении откроется окно входа, +а вы введёте логин, пароль и одноразовый код сами. + +Дополнительные удобства можно включить сейчас или позже. +``` + +Основное действие по Enter: продолжить с ручным безопасным режимом. + +Запрещено на первом экране: + +- `credential_source` +- `auth_profile` +- `TOTP seed` +- `openconnect-lite` +- `Bitwarden item` +- `headless` + +### Шаг 1: Что Будет Установлено + +Installer показывает не список пакетов, а понятные пункты: + +```text +Будет установлено: +- приложение Lemana VPN в меню macOS; +- команда vpn для подключения из Terminal; +- VPN-компонент, который открывает корпоративную страницу входа; +- безопасная очистка DNS после отключения. +``` + +Технические зависимости (`openconnect`, `pipx`, `openconnect-lite`) можно показывать только в подробном логе ниже или в debug/dry-run. + +### Шаг 2: Базовый Режим Входа + +Вопрос: + +```text +Как подключаться по умолчанию? + +1) Вводить всё вручную (рекомендуется) + Откроется окно входа. Вы сами введёте логин, пароль и одноразовый код. + +2) Запомнить логин и пароль на этом Mac + При входе логин и пароль подставятся сами. Одноразовый код вы введёте вручную. + +3) Настроить автоматический вход + Нужно заранее сохранить пароль и постоянный код настройки 2FA. + +Выбор [1/2/3, Enter=1]: +``` + +Поведение: + +- `1` ставит `auth_profile=manual`, `credential_source=none`. +- `2` ставит `auth_profile=saved-login`, `credential_source=keychain`, после установки запускает настройку LDAP username/password. +- `3` открывает следующий уточняющий шаг и не должен быть default. + +### Шаг 3: Автоматический Вход Только Для Продвинутого Пути + +Если выбран пункт 3, installer объясняет риск простым языком: + +```text +Автоматический вход работает только если на Mac сохранены: +- корпоративный пароль; +- постоянный код настройки 2FA. + +Это не текущие 6 цифр из приложения-аутентификатора. +Если вы не знаете, что это такое, выберите ручной режим. +``` + +Варианты: + +```text +1) Вернуться к ручному режиму +2) Сохранить пароль и постоянный код 2FA в macOS Keychain +3) Взять пароль и код 2FA из Bitwarden +``` + +Поведение: + +- `1` возвращает к `auth_profile=manual`. +- `2` ставит `auth_profile=saved-totp`, `credential_source=keychain`, запускает full Keychain setup. +- `3` ставит `credential_source=bitwarden`; Touch ID спрашивается отдельным простым вопросом. + +### Шаг 4: Bitwarden Как Дополнительная Функция + +Bitwarden не должен выглядеть обязательным. Вопрос появляется только после явного выбора Bitwarden: + +```text +Использовать Bitwarden для хранения VPN-пароля? + +Подойдёт, если у вас уже есть запись с корпоративным VPN-паролем +и постоянным кодом настройки 2FA. +``` + +Если Bitwarden CLI не найден: + +```text +Bitwarden не найден на этом Mac. +Можно установить его сейчас или вернуться к ручному режиму. +``` + +Default: вернуться к ручному режиму, если пользователь не уверен. + +### Шаг 5: Touch ID Только После Bitwarden + +Вопрос: + +```text +Разрешить Touch ID для открытия Bitwarden? + +Это не VPN-пароль. Touch ID только помогает достать данные из Bitwarden. +``` + +Default: нет, если пользователь не выбирал Bitwarden; да/нет можно спрашивать только в Bitwarden path. + +### Шаг 6: Понятный Прогресс Установки + +Progress log должен говорить человеческим языком: + +```text +Проверяю компоненты для VPN... +Устанавливаю приложение Lemana VPN... +Добавляю команду vpn... +Настраиваю безопасное отключение VPN... +Готово. +``` + +Если macOS просит пароль администратора, installer заранее объясняет: + +```text +macOS может запросить пароль от компьютера. +Он нужен, чтобы разрешить VPN подключаться и корректно очищать DNS после отключения. +``` + +### Финальный Экран + +Для ручного режима: + +```text +Готово. Как подключиться: +1. Откройте Lemana VPN из Applications или меню macOS. +2. Нажмите "Подключить". +3. В открывшемся окне введите корпоративный логин, пароль и одноразовый код. + +Проверить состояние: vpn --status +``` + +Для режима с сохранённым логином и паролем: + +```text +Готово. При подключении логин и пароль будут подставлены автоматически. +Одноразовый код нужно ввести вручную. +``` + +Для автоматического режима: + +```text +Готово. Автоматический вход включён. +Если он не сработает, используйте ручной режим: vpn --manual-full +``` + +### Ошибки И Подсказки + +Ошибки должны говорить, что делать дальше: + +- Нет сохранённого пароля: `Запустите vpn --configure-login или подключитесь вручную.` +- Нет постоянного кода 2FA для auto mode: `Автоматический вход не настроен. Выберите ручной режим или добавьте постоянный код настройки 2FA.` +- Bitwarden заблокирован в приложении без Terminal: `Откройте Terminal и выполните vpn, либо подключитесь вручную.` +- Пользователь выбрал непонятный вариант: `Введите 1, 2 или 3. Если не уверены, нажмите Enter.` + +### Acceptance Evidence Для Installer UX + +- Interactive dry-run через `expect` должен захватить первый экран, выбор режима и финальную подсказку. +- Smoke должен проверять, что Enter выбирает ручной режим. +- README должен показывать тот же сценарий, что и installer. +- В обычном wizard не должно быть слов `credential_source`, `auth_profile`, `TOTP seed`; они допустимы только в help/debug/docs for advanced. + +## Дополнительные Функции + +1. **Core manual SSO (default)** + - Что делает: открывает видимый браузер Keycloak, не читает Bitwarden/Keychain, не заполняет поля, не нажимает submit. + - Когда включается: всегда на fresh install и при `vpn` без дополнительных настроек. + - Когда не включается: если пользователь явно запускает `vpn-auto` или выбрал auto profile. + +2. **Saved LDAP login/password in macOS Keychain** + - Что делает: сохраняет LDAP username/password в Keychain и подставляет их в видимую форму. + - Когда включается: пользователь явно запускает настройку сохранения LDAP или выбирает этот пункт в installer. + - OTP: текущий одноразовый код вводится руками; TOTP seed не требуется. + +3. **Saved TOTP seed** + - Что делает: сохраняет постоянный TOTP seed и позволяет `openconnect-lite` генерировать текущий код для autofill. + - Когда включается: пользователь явно добавил seed через full Keychain setup или Bitwarden sync. + - Ограничение: текущий 6-значный код нельзя сохранять как настройку. + +4. **Autofill** + - Что делает: заполняет те поля, для которых есть capability: username/password, а затем optional TOTP. + - Когда включается: при `saved-login` или `saved-totp`. + - Submit: по умолчанию выключен; пользователь нажимает вход сам. + +5. **Autosubmit/headless** + - Что делает: запускает старый удобный путь с hidden browser, полным autofill и нажатием submit. + - Когда включается: только явным `vpn-auto`, `--auto`, profile `auto` или отдельным opt-in флагом. + - Предусловие: saved login/password и TOTP seed должны быть доступны; без TOTP seed этот режим должен fail fast с понятной ошибкой. + +6. **Bitwarden sync** + - Что делает: читает LDAP password и TOTP seed из Bitwarden item, нормализует TOTP seed и перекладывает в Keychain. + - Когда включается: только если пользователь выбрал Bitwarden provider. + - Fallback: если `bw` отсутствует или locked в GUI/no-tty, использовать уже сохранённый Keychain либо manual path, не блокировать default. + +7. **Touch ID helper** + - Что делает: локально защищает чтение Bitwarden master password. + - Когда включается: только вместе с Bitwarden provider и явным согласием пользователя. + - Не делает: не заменяет LDAP password и не является хранением VPN credentials. + +## Tasks + +### Task 1: Rewrite user-facing contract and docs + +- Scope: `README.md`. +- Change: describe manual-first default, feature matrix, migration from old Bitwarden-first install, and the non-IT installation scenario from this plan. +- Expected output: docs no longer say Bitwarden/auto are default; examples show `vpn` as manual and `vpn-auto` as opt-in; installation instructions use the same wording as the installer. +- Verification: `rg -n "по умолчанию|Bitwarden|vpn-auto|manual-full|одноразовый код|ручной режим" README.md`. +- Acceptance evidence: a new user can read the first install section and understand what will be installed, what they will type during first connect, and that Bitwarden is optional. +- Parallel: yes, but must be reconciled with installer flag names from Task 2. + +### Task 2: Change installer defaults and capability selection + +- Scope: `install.sh`. +- Change: default credential source becomes none/manual; Bitwarden and Touch ID default off; wizard Enter keeps manual; add or reuse flags for saved LDAP, full Keychain, Bitwarden provider, and auto profile; rewrite interactive copy using the "Пользовательский Процесс Установки" section. +- Expected output: dry-run shows manual/default auth and no Bitwarden/Touch ID install unless selected; interactive prompts say "вводить всё вручную", "запомнить логин и пароль", and "автоматический вход" instead of exposing low-level config names. +- Verification: `sh -n install.sh`; `sh install.sh --dry-run --non-interactive`; interactive dry-run through `expect` if available. +- Acceptance evidence: clean install dry-run does not try to install Bitwarden and does not schedule Keychain credential prompts by default; captured interactive output is understandable without IT vocabulary and Enter chooses manual mode. +- Parallel: no, because it defines env contract consumed by CLI/tests. + +### Task 3: Refactor CLI auth profiles and partial Keychain support + +- Scope: `bin/vpn-lemanapro.sh`. +- Change: replace auto-first/default credential preflight with an auth-profile resolver; add partial Keychain support for saved login without TOTP; keep full Keychain and Bitwarden sync as opt-ins. +- Expected output: no-credential default reaches visible manual browser; saved-login fills username/password and leaves OTP manual; saved-totp can fill OTP but still does not submit by default. +- Verification: `bash -n bin/vpn-lemanapro.sh`; fake `openconnect-lite` capture in `tests/smoke.sh`. +- Acceptance evidence: JSON events clearly state active auth profile and missing optional capabilities without treating them as fatal in manual profiles. +- Parallel: no, central truth owner. + +### Task 4: Preserve automatic compatibility without keeping it dominant + +- Scope: `bin/vpn-lemanapro.sh`, `install.sh`, shell aliases. +- Change: keep `vpn-auto`/`--auto` as explicit old behavior; guard autosubmit/headless behind full saved credentials; keep `--manual-full` compatibility alias for the default manual profile. +- Expected output: current power users can still run the old path intentionally. +- Verification: `tests/smoke.sh` assertions for `vpn-auto`, `--auto`, `--manual-full`, launch env. +- Acceptance evidence: default path and auto path are both test-covered and visibly separate. +- Parallel: can run after Task 3. + +### Task 5: Update Swift menu-bar app surface + +- Scope: `app/Sources/LemanaVPN/VPNManager.swift`, `app/Sources/LemanaVPN/LemanaVPNApp.swift`. +- Change: make the primary menu action manual-first; demote automatic connect to an explicit secondary action; keep app as a CLI wrapper; surface missing saved credentials as setup guidance, not hidden prompts. +- Expected output: app default action does not trigger Bitwarden or autosubmit unless the user chooses an explicit auto/saved mode. +- Verification: `swift build -c release --package-path app`; smoke greps for the new labels/mode wiring. +- Acceptance evidence: app source and built binary contain the manual-first label and map it to the manual profile. +- Parallel: yes after CLI mode names are stable. + +### Task 6: Expand smoke coverage + +- Scope: `tests/smoke.sh`. +- Change: update old assertions and add cases for fresh manual default, saved-login-only, saved-totp, Bitwarden opt-in, and app wiring. +- Expected output: smoke becomes the executable spec for the feature matrix and installer UX. +- Verification: `sh -n tests/smoke.sh`; `tests/smoke.sh`. +- Acceptance evidence: smoke fails if Bitwarden or auto become fresh defaults again, or if the basic wizard no longer defaults to manual mode with non-technical wording. +- Parallel: yes after Task 2 and Task 3 contracts are known. + +### Task 7: Final verification and local update notes + +- Scope: whole repo; installed app only if doing a local refresh. +- Change: run non-live ladder, then optionally install/update local checkout with `sh install.sh` after user approval for real local changes. +- Expected output: plan implemented with evidence and a clear "live VPN not run" or live manual evidence. +- Verification: + - `sh -n install.sh` + - `sh -n uninstall.sh` + - `bash -n bin/vpn-lemanapro.sh` + - `sh -n tests/smoke.sh` + - `tests/smoke.sh` + - `swift build -c release --package-path app` + - `git diff --check` +- Acceptance evidence: final answer includes how to run, how to check, how to update installed setup, and what live checks were intentionally skipped. +- Parallel: no, final integration gate. + +## Non-Goals + +- Do not replace `openconnect-lite` or implement a separate VPN client in Swift. +- Do not store current 6-digit OTP codes. +- Do not make the GUI collect secrets directly. +- Do not remove existing automatic mode; demote it to explicit opt-in. +- Do not change DNS cleanup behavior as part of this refactor. + +## Risks + +- Existing users may expect `vpn` to auto-connect. Mitigation: keep `vpn-auto` and document the cutover clearly. +- Partial Keychain support may need careful `openconnect-lite` behavior verification when TOTP is absent. Mitigation: fake launch tests first, then one visible manual smoke if needed. +- Swift app could lag behind installed source again. Mitigation: after implementation refresh installed bundle via `sh install.sh` and verify with app source/build or `strings` if doing local install. +- Credential language can confuse Bitwarden master password, LDAP password, TOTP seed, and current OTP. Mitigation: keep labels explicit in CLI and README. + +## Execution Order + +1. Task 2 defines the installer/env contract. +2. Task 3 implements the CLI truth owner. +3. Task 4 preserves explicit auto compatibility. +4. Task 5 updates the app wrapper. +5. Task 6 turns the matrix into smoke coverage. +6. Task 1 updates docs with final flag/profile names. +7. Task 7 verifies and prepares local update instructions. diff --git a/tests/smoke.sh b/tests/smoke.sh index 9cda20e..b9c7b13 100755 --- a/tests/smoke.sh +++ b/tests/smoke.sh @@ -81,6 +81,7 @@ grep -q 'vpn-manual-full' "$ROOT/install.sh" grep -q 'connect(mode: .auto)' "$ROOT/app/Sources/LemanaVPN/LemanaVPNApp.swift" grep -q 'connect(mode: .manual)' "$ROOT/app/Sources/LemanaVPN/LemanaVPNApp.swift" grep -q 'connect(mode: .manualFull)' "$ROOT/app/Sources/LemanaVPN/LemanaVPNApp.swift" +grep -q 'Подключить полностью вручную' "$ROOT/app/Sources/LemanaVPN/LemanaVPNApp.swift" grep -q 'enum VPNLaunchMode' "$ROOT/app/Sources/LemanaVPN/VPNManager.swift" grep -q 'struct Credentials: Decodable' "$ROOT/app/Sources/LemanaVPN/VPNManager.swift" grep -q 'credential_source' "$ROOT/app/Sources/LemanaVPN/VPNManager.swift"