Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1f74bd814f | ||
|
|
d9d93574f2 | ||
|
|
15e8ed1ec2 | ||
|
|
1df0c8e96b | ||
|
|
8213a8b451 | ||
|
|
af77fd4a21 | ||
|
|
85d0cab0f5 | ||
|
|
e8cdcdfb7f | ||
|
|
d7f8d5e56b | ||
|
|
3f247d628f | ||
|
|
dae9cead4e | ||
|
|
fe9f8613fa | ||
|
|
5d5ae5bab4 | ||
|
|
f76766a9d5 | ||
|
|
b308b10716 | ||
|
|
0e1e6813a1 | ||
|
|
27bf7c29e4 | ||
|
|
36b6c52979 | ||
|
|
67ec4fee12 | ||
|
|
21fc834fd9 | ||
|
|
726d0580d0 | ||
|
|
4f608ebbc6 | ||
|
|
6e7cf82618 |
6
.github/workflows/release.yml
vendored
@@ -55,7 +55,7 @@ jobs:
|
||||
export CLEAN_TAG=$(echo $TAG_NAME | sed -e 's/refs\/tags\/v//')
|
||||
sed -i '' -e "s/GITHUB_CI_VERSION/$CLEAN_TAG/g" Sources/Config/Config.xcconfig
|
||||
sed -i '' -e "s/GITHUB_BUILD_NUMBER/1.$RUN_ID/g" Sources/Config/Config.xcconfig
|
||||
sed -i '' -e "s/GITHUB_BUILD_URL/https:\/\/github.com\/maxgoedjen\/secretive\/actions\/runs\/$RUN_ID/g" Sources/Secretive/Credits.rtf
|
||||
sed -i '' -e "s/GITHUB_BUILD_URL/https:\/\/github.com\/maxgoedjen\/secretive\/actions\/runs\/$RUN_ID/g" Sources/Config/Config.xcconfig
|
||||
- name: Build
|
||||
run: xcrun xcodebuild -project Sources/Secretive.xcodeproj -scheme Secretive -configuration Release -archivePath Archive.xcarchive archive
|
||||
- name: Create ZIP
|
||||
@@ -77,13 +77,13 @@ jobs:
|
||||
uses: actions/attest-build-provenance@v2
|
||||
with:
|
||||
subject-name: "Secretive.zip"
|
||||
subject-digest: ${{ steps.upload.outputs.artifact-digest }}
|
||||
subject-digest: sha256:${{ steps.upload.outputs.artifact-digest }}
|
||||
- name: Create Release
|
||||
run: |
|
||||
sed -i.tmp "s/RUN_ID/$RUN_ID/g" .github/templates/release.md
|
||||
sed -i.tmp "s/ATTESTATION_ID/$ATTESTATION_ID/g" .github/templates/release.md
|
||||
gh release create $TAG_NAME -d -F .github/templates/release.md
|
||||
gh release upload Secretive.zip
|
||||
gh release upload $TAG_NAME Secretive.zip
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
TAG_NAME: ${{ github.ref }}
|
||||
|
||||
11
README.md
@@ -1,8 +1,7 @@
|
||||
# Secretive [](https://github.com/maxgoedjen/secretive/actions/workflows/test.yml) 
|
||||
|
||||
|
||||
Secretive is an app for storing and managing SSH keys in the Secure Enclave. It is inspired by the [sekey project](https://github.com/sekey/sekey), but rewritten in Swift with no external dependencies and with a handy native management app.
|
||||
|
||||
Secretive is an app for protecting and managing SSH keys with the Secure Enclave.
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="/.github/readme/app-dark.png">
|
||||
<img src="/.github/readme/app-light.png" alt="Screenshot of Secretive" width="600">
|
||||
@@ -62,3 +61,11 @@ Because secrets in the Secure Enclave are not exportable, they are not able to b
|
||||
## Security
|
||||
|
||||
Secretive's security policy is detailed in [SECURITY.md](SECURITY.md). To report security issues, please use [GitHub's private reporting feature.](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability#privately-reporting-a-security-vulnerability)
|
||||
|
||||
## Acknowledgements
|
||||
|
||||
### sekey
|
||||
Secretive was inspired by the [sekey project](https://github.com/sekey/sekey).
|
||||
|
||||
### Localization
|
||||
Secretive is localized to many languages by a generous team of volunteers. To learn more, see [LOCALIZING.md](LOCALIZING.md). Secretive's localization workflow is generously provided by [Crowdin](https://crowdin.com).
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
CI_VERSION = GITHUB_CI_VERSION
|
||||
CI_BUILD_NUMBER = GITHUB_BUILD_NUMBER
|
||||
CI_BUILD_LINK = GITHUB_BUILD_URL
|
||||
|
||||
@@ -177,6 +177,73 @@
|
||||
"value" : ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"shouldTranslate" : false
|
||||
},
|
||||
"**%@** (%@)" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "new",
|
||||
"value" : "**%1$@** (%2$@)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"shouldTranslate" : false
|
||||
},
|
||||
"about_build_log_button" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Build Log"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"about_menu_bar_title" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "About Secretive"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"about_open_source_notice" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Secretive is Open Source and MIT Licensed"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"about_thanks" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Special thanks our [Contributors](%1$(contributorsLink)@) and [Sponsors](%2$(sponsorsLink)@)"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"about_view_on_github_button" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "View on GitHub"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"agent_details_could_not_start_error" : {
|
||||
@@ -316,8 +383,8 @@
|
||||
},
|
||||
"ru" : {
|
||||
"stringUnit" : {
|
||||
"state" : "new",
|
||||
"value" : "Secretive was unable to get SecretAgent to launch. Please try restarting your Mac, and if that doesn't work, file an issue on GitHub."
|
||||
"state" : "translated",
|
||||
"value" : "Secretive не получлось запустить SecretAgent. Пожалуйста, попробуйте перезапустить Ваш Mac, и если это не поможет – создайте issue на GitHub."
|
||||
}
|
||||
},
|
||||
"sr" : {
|
||||
@@ -501,8 +568,8 @@
|
||||
},
|
||||
"ru" : {
|
||||
"stringUnit" : {
|
||||
"state" : "new",
|
||||
"value" : "Disable Agent"
|
||||
"state" : "translated",
|
||||
"value" : "Отключить агент"
|
||||
}
|
||||
},
|
||||
"sr" : {
|
||||
@@ -686,8 +753,8 @@
|
||||
},
|
||||
"ru" : {
|
||||
"stringUnit" : {
|
||||
"state" : "new",
|
||||
"value" : "Restart Agent"
|
||||
"state" : "translated",
|
||||
"value" : "Перезапустить агент"
|
||||
}
|
||||
},
|
||||
"sr" : {
|
||||
@@ -871,8 +938,8 @@
|
||||
},
|
||||
"ru" : {
|
||||
"stringUnit" : {
|
||||
"state" : "new",
|
||||
"value" : "Running Since"
|
||||
"state" : "translated",
|
||||
"value" : "Запущено с"
|
||||
}
|
||||
},
|
||||
"sr" : {
|
||||
@@ -1056,8 +1123,8 @@
|
||||
},
|
||||
"ru" : {
|
||||
"stringUnit" : {
|
||||
"state" : "new",
|
||||
"value" : "Socket Path"
|
||||
"state" : "translated",
|
||||
"value" : "Путь к сокету"
|
||||
}
|
||||
},
|
||||
"sr" : {
|
||||
@@ -1241,8 +1308,8 @@
|
||||
},
|
||||
"ru" : {
|
||||
"stringUnit" : {
|
||||
"state" : "new",
|
||||
"value" : "Start Agent"
|
||||
"state" : "translated",
|
||||
"value" : "Запустить агент"
|
||||
}
|
||||
},
|
||||
"sr" : {
|
||||
@@ -1426,8 +1493,8 @@
|
||||
},
|
||||
"ru" : {
|
||||
"stringUnit" : {
|
||||
"state" : "new",
|
||||
"value" : "Starting Agent"
|
||||
"state" : "translated",
|
||||
"value" : "Агент запускается"
|
||||
}
|
||||
},
|
||||
"sr" : {
|
||||
@@ -1611,8 +1678,8 @@
|
||||
},
|
||||
"ru" : {
|
||||
"stringUnit" : {
|
||||
"state" : "new",
|
||||
"value" : "Version"
|
||||
"state" : "translated",
|
||||
"value" : "Версия"
|
||||
}
|
||||
},
|
||||
"sr" : {
|
||||
@@ -1796,8 +1863,8 @@
|
||||
},
|
||||
"ru" : {
|
||||
"stringUnit" : {
|
||||
"state" : "new",
|
||||
"value" : "SecretAgent is a process that runs in the background to sign requests, so you don't need to keep Secretive open all the time.\n\n**Secretive will not be able to function properly unless the agent is installed and running.**"
|
||||
"state" : "translated",
|
||||
"value" : "SecretAgent это процесс, который работает в фоне чтобы подписывать запросы. Так Вам не придется все время держать Secretive открытым.\n\n**Secretive не сможет нормально функционировать, пока агент не установлен и не запущен.**"
|
||||
}
|
||||
},
|
||||
"sr" : {
|
||||
@@ -2721,8 +2788,8 @@
|
||||
},
|
||||
"ru" : {
|
||||
"stringUnit" : {
|
||||
"state" : "new",
|
||||
"value" : "Secret Agent Location"
|
||||
"state" : "translated",
|
||||
"value" : "Расположение Secret Agent"
|
||||
}
|
||||
},
|
||||
"sr" : {
|
||||
@@ -3139,6 +3206,23 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"app_not_in_applications_notice_cancel_button" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Later"
|
||||
}
|
||||
},
|
||||
"ru" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Позже"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"app_not_in_applications_notice_detail_description" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
@@ -3324,6 +3408,23 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"app_not_in_applications_notice_quit_button" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Quit"
|
||||
}
|
||||
},
|
||||
"ru" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Выйти"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"app_not_in_applications_notice_title" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
@@ -4574,8 +4675,8 @@
|
||||
},
|
||||
"ru" : {
|
||||
"stringUnit" : {
|
||||
"state" : "new",
|
||||
"value" : "Advanced"
|
||||
"state" : "translated",
|
||||
"value" : "Расширенное"
|
||||
}
|
||||
},
|
||||
"sr" : {
|
||||
@@ -4759,8 +4860,8 @@
|
||||
},
|
||||
"ru" : {
|
||||
"stringUnit" : {
|
||||
"state" : "new",
|
||||
"value" : "If you change your biometric settings in _any way_, including adding a new fingerprint, this key will no longer be accessible."
|
||||
"state" : "translated",
|
||||
"value" : "Если Вы сделаете _любые_ изменения в Ваших настройках биометрии, включая добавление нового отпечатка пальца, этот ключ перестанет быть доступным."
|
||||
}
|
||||
},
|
||||
"sr" : {
|
||||
@@ -5314,8 +5415,8 @@
|
||||
},
|
||||
"ru" : {
|
||||
"stringUnit" : {
|
||||
"state" : "new",
|
||||
"value" : "This shows at the end of your public key. It’s usually an email address."
|
||||
"state" : "translated",
|
||||
"value" : "Это отображается в конце Вашего публичного ключа. Как правило, это email адрес."
|
||||
}
|
||||
},
|
||||
"sr" : {
|
||||
@@ -5499,8 +5600,8 @@
|
||||
},
|
||||
"ru" : {
|
||||
"stringUnit" : {
|
||||
"state" : "new",
|
||||
"value" : "Key Attribution"
|
||||
"state" : "translated",
|
||||
"value" : "Принадлежность ключа"
|
||||
}
|
||||
},
|
||||
"sr" : {
|
||||
@@ -5684,8 +5785,8 @@
|
||||
},
|
||||
"ru" : {
|
||||
"stringUnit" : {
|
||||
"state" : "new",
|
||||
"value" : "Key Type"
|
||||
"state" : "translated",
|
||||
"value" : "Тип ключа"
|
||||
}
|
||||
},
|
||||
"sr" : {
|
||||
@@ -5732,6 +5833,17 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"create_secret_key_type_macOS_update_required_label" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Unavailable on this version of macOS"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"create_secret_mldsa_warning" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
@@ -5869,8 +5981,8 @@
|
||||
},
|
||||
"ru" : {
|
||||
"stringUnit" : {
|
||||
"state" : "new",
|
||||
"value" : "Warning: ML-DSA keys are very new, and not supported by many servers yet. Please verify the server you'll be using this key for accepts ML-DSA keys."
|
||||
"state" : "translated",
|
||||
"value" : "Внимание: Ключи ML-DSA совсем новые, и не пока поддерживаются многими серверами. Пожалуйста удостоверьтесь, что используемый с этим ключом сервер поддерживает ключи ML-DSA."
|
||||
}
|
||||
},
|
||||
"sr" : {
|
||||
@@ -6425,7 +6537,7 @@
|
||||
"ru" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Аутентификация не потребуется пока Ваш Mac разблокирован, но Вы получите уведомление, если секрет был использован."
|
||||
"value" : "Аутентификация не потребуется пока Ваш Mac разблокирован, но Вы получите уведомление, если секрет будет использован."
|
||||
}
|
||||
},
|
||||
"sr" : {
|
||||
@@ -6794,8 +6906,8 @@
|
||||
},
|
||||
"ru" : {
|
||||
"stringUnit" : {
|
||||
"state" : "new",
|
||||
"value" : "Protection Level"
|
||||
"state" : "translated",
|
||||
"value" : "Уровень защиты"
|
||||
}
|
||||
},
|
||||
"sr" : {
|
||||
@@ -6979,8 +7091,8 @@
|
||||
},
|
||||
"ru" : {
|
||||
"stringUnit" : {
|
||||
"state" : "new",
|
||||
"value" : "Require authentication with current set of biometrics."
|
||||
"state" : "translated",
|
||||
"value" : "Требовать аутентификацию с текущим набором биометрии."
|
||||
}
|
||||
},
|
||||
"sr" : {
|
||||
@@ -7164,8 +7276,8 @@
|
||||
},
|
||||
"ru" : {
|
||||
"stringUnit" : {
|
||||
"state" : "new",
|
||||
"value" : "Current Biometrics"
|
||||
"state" : "translated",
|
||||
"value" : "Текущая биометрия"
|
||||
}
|
||||
},
|
||||
"sr" : {
|
||||
@@ -9384,8 +9496,8 @@
|
||||
},
|
||||
"ru" : {
|
||||
"stringUnit" : {
|
||||
"state" : "new",
|
||||
"value" : "It looks like you may have recently updated macOS. Sometimes this puts the Secure Enclave into a weird state, and you might need to reboot your Mac before things start working again."
|
||||
"state" : "translated",
|
||||
"value" : "Похоже, что Вы могли недавно обновить MacOS. Иногда из-за этого возникают проблемы с Secure Enclave, и возможно Вам потребуется перезапустить Ваш Mac, чтобы их исправить."
|
||||
}
|
||||
},
|
||||
"sr" : {
|
||||
@@ -9569,8 +9681,8 @@
|
||||
},
|
||||
"ru" : {
|
||||
"stringUnit" : {
|
||||
"state" : "new",
|
||||
"value" : "Missing Secrets?"
|
||||
"state" : "translated",
|
||||
"value" : "Не хватает секретов?"
|
||||
}
|
||||
},
|
||||
"sr" : {
|
||||
@@ -10667,8 +10779,8 @@
|
||||
},
|
||||
"ru" : {
|
||||
"stringUnit" : {
|
||||
"state" : "new",
|
||||
"value" : "Add This:"
|
||||
"state" : "translated",
|
||||
"value" : "Добавить следующее:"
|
||||
}
|
||||
},
|
||||
"sr" : {
|
||||
@@ -10852,8 +10964,8 @@
|
||||
},
|
||||
"ru" : {
|
||||
"stringUnit" : {
|
||||
"state" : "new",
|
||||
"value" : "Apps"
|
||||
"state" : "translated",
|
||||
"value" : "Приложения"
|
||||
}
|
||||
},
|
||||
"sr" : {
|
||||
@@ -11037,8 +11149,8 @@
|
||||
},
|
||||
"ru" : {
|
||||
"stringUnit" : {
|
||||
"state" : "new",
|
||||
"value" : "There's a community-maintained list of instructions for apps on GitHub. If the app you're looking for isn't supported, create an issue and the community may be able to help."
|
||||
"state" : "translated",
|
||||
"value" : "Существует список инструкций, поддерживаемый сообществом, к приложениям на GitHub. Если нужное Вам приложение не поддерживается, создайте issue, возможно сообщество сможет помочь Вам."
|
||||
}
|
||||
},
|
||||
"sr" : {
|
||||
@@ -11222,8 +11334,8 @@
|
||||
},
|
||||
"ru" : {
|
||||
"stringUnit" : {
|
||||
"state" : "new",
|
||||
"value" : "There's a community-maintained list of shell instructions on GitHub. If the shell you're looking for isn't supported, create an issue and the community may be able to help."
|
||||
"state" : "translated",
|
||||
"value" : "Существует список shell-скриптов, поддерживаемый сообществом на GitHub. Если нужный Вам shell-скрипт не поддерживается, создайте issue, возможно сообщество сможет помочь Вам."
|
||||
}
|
||||
},
|
||||
"sr" : {
|
||||
@@ -11407,8 +11519,8 @@
|
||||
},
|
||||
"ru" : {
|
||||
"stringUnit" : {
|
||||
"state" : "new",
|
||||
"value" : "You'll need to create a Secret before configuring this action."
|
||||
"state" : "translated",
|
||||
"value" : "Вам потребуется создать секрет перед настройкой этого действия."
|
||||
}
|
||||
},
|
||||
"sr" : {
|
||||
@@ -11592,8 +11704,8 @@
|
||||
},
|
||||
"ru" : {
|
||||
"stringUnit" : {
|
||||
"state" : "new",
|
||||
"value" : "Configure Using Secret"
|
||||
"state" : "translated",
|
||||
"value" : "Настроить используя секрет"
|
||||
}
|
||||
},
|
||||
"sr" : {
|
||||
@@ -11777,8 +11889,8 @@
|
||||
},
|
||||
"ru" : {
|
||||
"stringUnit" : {
|
||||
"state" : "new",
|
||||
"value" : "No Secret"
|
||||
"state" : "translated",
|
||||
"value" : "Без секрета"
|
||||
}
|
||||
},
|
||||
"sr" : {
|
||||
@@ -11962,8 +12074,8 @@
|
||||
},
|
||||
"ru" : {
|
||||
"stringUnit" : {
|
||||
"state" : "new",
|
||||
"value" : "Secret"
|
||||
"state" : "translated",
|
||||
"value" : "С секретом"
|
||||
}
|
||||
},
|
||||
"sr" : {
|
||||
@@ -12147,8 +12259,8 @@
|
||||
},
|
||||
"ru" : {
|
||||
"stringUnit" : {
|
||||
"state" : "new",
|
||||
"value" : "You can configure more than one tool, they generally won't interfere with each other."
|
||||
"state" : "translated",
|
||||
"value" : "Вы можете настроить более чем один инструмент. Обычно это не вызывает конфликтов при работе."
|
||||
}
|
||||
},
|
||||
"sr" : {
|
||||
@@ -12332,8 +12444,8 @@
|
||||
},
|
||||
"ru" : {
|
||||
"stringUnit" : {
|
||||
"state" : "new",
|
||||
"value" : "Getting Started"
|
||||
"state" : "translated",
|
||||
"value" : "Начало работы"
|
||||
}
|
||||
},
|
||||
"sr" : {
|
||||
@@ -12517,8 +12629,8 @@
|
||||
},
|
||||
"ru" : {
|
||||
"stringUnit" : {
|
||||
"state" : "new",
|
||||
"value" : "Integrations"
|
||||
"state" : "translated",
|
||||
"value" : "Интеграции"
|
||||
}
|
||||
},
|
||||
"sr" : {
|
||||
@@ -12702,8 +12814,8 @@
|
||||
},
|
||||
"ru" : {
|
||||
"stringUnit" : {
|
||||
"state" : "new",
|
||||
"value" : "If you're trying to sign your git commits, set up Git Signing."
|
||||
"state" : "translated",
|
||||
"value" : "Если Вы хотите подписывать ваши git коммиты, настройте подпись Git."
|
||||
}
|
||||
},
|
||||
"sr" : {
|
||||
@@ -12887,8 +12999,8 @@
|
||||
},
|
||||
"ru" : {
|
||||
"stringUnit" : {
|
||||
"state" : "new",
|
||||
"value" : "If you're trying to configure anything your command line runs to use Secretive, configure your shell."
|
||||
"state" : "translated",
|
||||
"value" : "Если Вы хотите наладить работу Secretive с Вашими скриптами в Терминале, настройте Ваш Терминал."
|
||||
}
|
||||
},
|
||||
"sr" : {
|
||||
@@ -13072,8 +13184,8 @@
|
||||
},
|
||||
"ru" : {
|
||||
"stringUnit" : {
|
||||
"state" : "new",
|
||||
"value" : "If you don't known what shell you use and haven't changed it, you're probably using `%(shellName)@`."
|
||||
"state" : "translated",
|
||||
"value" : "Если Вы не знаете, какой Терминал используете и не меняли его раннее, то скорее всего Вы используете `%(shellName)@`."
|
||||
}
|
||||
},
|
||||
"sr" : {
|
||||
@@ -13257,8 +13369,8 @@
|
||||
},
|
||||
"ru" : {
|
||||
"stringUnit" : {
|
||||
"state" : "new",
|
||||
"value" : "If you're trying to authenticate with an SSH server or authenticating with a service like GitHub over SSH, configure your SSH client."
|
||||
"state" : "translated",
|
||||
"value" : "Если Вы хотите аутентифицироваться с SSH сервером, или с сервисом как GitHub через SSH, настройте Ваш SSH клиент."
|
||||
}
|
||||
},
|
||||
"sr" : {
|
||||
@@ -13442,8 +13554,8 @@
|
||||
},
|
||||
"ru" : {
|
||||
"stringUnit" : {
|
||||
"state" : "new",
|
||||
"value" : "Configuring Tools for Secretive"
|
||||
"state" : "translated",
|
||||
"value" : "Настройка инструментов для Secretive"
|
||||
}
|
||||
},
|
||||
"sr" : {
|
||||
@@ -13627,8 +13739,8 @@
|
||||
},
|
||||
"ru" : {
|
||||
"stringUnit" : {
|
||||
"state" : "new",
|
||||
"value" : "Most tools will try and look for SSH keys on disk in `~/.ssh`. To use Secretive, we need to configure those tools to talk to Secretive instead."
|
||||
"state" : "translated",
|
||||
"value" : "Большинство инструментов попробуют найти SSH ключи в `~/.ssh`. Для того, чтобы использовать Secretive, нам потребуется настроить эти инструменты, чтобы они работали с Secretive."
|
||||
}
|
||||
},
|
||||
"sr" : {
|
||||
@@ -13812,8 +13924,8 @@
|
||||
},
|
||||
"ru" : {
|
||||
"stringUnit" : {
|
||||
"state" : "new",
|
||||
"value" : "What Should I Configure?"
|
||||
"state" : "translated",
|
||||
"value" : "Что мне следует настроить?"
|
||||
}
|
||||
},
|
||||
"sr" : {
|
||||
@@ -13997,8 +14109,8 @@
|
||||
},
|
||||
"ru" : {
|
||||
"stringUnit" : {
|
||||
"state" : "new",
|
||||
"value" : "~/.gitallowedsigners probably does not exist. You'll need to create it."
|
||||
"state" : "translated",
|
||||
"value" : "Возможно, директория ~/.gitallowedsigners не существует. Вам потребуется создать ее."
|
||||
}
|
||||
},
|
||||
"sr" : {
|
||||
@@ -14368,8 +14480,8 @@
|
||||
},
|
||||
"ru" : {
|
||||
"stringUnit" : {
|
||||
"state" : "new",
|
||||
"value" : "Integrations…"
|
||||
"state" : "translated",
|
||||
"value" : "Интеграции…"
|
||||
}
|
||||
},
|
||||
"sr" : {
|
||||
@@ -14553,8 +14665,8 @@
|
||||
},
|
||||
"ru" : {
|
||||
"stringUnit" : {
|
||||
"state" : "new",
|
||||
"value" : "Other"
|
||||
"state" : "translated",
|
||||
"value" : "Другое"
|
||||
}
|
||||
},
|
||||
"sr" : {
|
||||
@@ -14738,8 +14850,8 @@
|
||||
},
|
||||
"ru" : {
|
||||
"stringUnit" : {
|
||||
"state" : "new",
|
||||
"value" : "other"
|
||||
"state" : "translated",
|
||||
"value" : "другое"
|
||||
}
|
||||
},
|
||||
"sr" : {
|
||||
@@ -14923,8 +15035,8 @@
|
||||
},
|
||||
"ru" : {
|
||||
"stringUnit" : {
|
||||
"state" : "new",
|
||||
"value" : "Configuration File"
|
||||
"state" : "translated",
|
||||
"value" : "Файл конфигурации"
|
||||
}
|
||||
},
|
||||
"sr" : {
|
||||
@@ -15108,7 +15220,7 @@
|
||||
},
|
||||
"ru" : {
|
||||
"stringUnit" : {
|
||||
"state" : "new",
|
||||
"state" : "translated",
|
||||
"value" : "Shell"
|
||||
}
|
||||
},
|
||||
@@ -15293,8 +15405,8 @@
|
||||
},
|
||||
"ru" : {
|
||||
"stringUnit" : {
|
||||
"state" : "new",
|
||||
"value" : "You can tell SSH to use a specific key for a given host. See the web documentation for more details."
|
||||
"state" : "translated",
|
||||
"value" : "Вы можете настроить SSH так, чтобы использовался конкретный ключ для выбранного хоста. Посмотрите документацию в интернете, чтобы узнать подробности."
|
||||
}
|
||||
},
|
||||
"sr" : {
|
||||
@@ -15478,8 +15590,8 @@
|
||||
},
|
||||
"ru" : {
|
||||
"stringUnit" : {
|
||||
"state" : "new",
|
||||
"value" : "System"
|
||||
"state" : "translated",
|
||||
"value" : "Система"
|
||||
}
|
||||
},
|
||||
"sr" : {
|
||||
@@ -16035,8 +16147,8 @@
|
||||
},
|
||||
"ru" : {
|
||||
"stringUnit" : {
|
||||
"state" : "new",
|
||||
"value" : "Git Signing"
|
||||
"state" : "translated",
|
||||
"value" : "Подпись Git"
|
||||
}
|
||||
},
|
||||
"sr" : {
|
||||
@@ -16592,8 +16704,8 @@
|
||||
},
|
||||
"ru" : {
|
||||
"stringUnit" : {
|
||||
"state" : "new",
|
||||
"value" : "View on GitHub"
|
||||
"state" : "translated",
|
||||
"value" : "Посмотреть на GitHub"
|
||||
}
|
||||
},
|
||||
"sr" : {
|
||||
@@ -16777,8 +16889,8 @@
|
||||
},
|
||||
"ru" : {
|
||||
"stringUnit" : {
|
||||
"state" : "new",
|
||||
"value" : "View Documentation on Web"
|
||||
"state" : "translated",
|
||||
"value" : "Найти документацию в интернете"
|
||||
}
|
||||
},
|
||||
"sr" : {
|
||||
@@ -16962,8 +17074,8 @@
|
||||
},
|
||||
"ru" : {
|
||||
"stringUnit" : {
|
||||
"state" : "new",
|
||||
"value" : "If any section (like [user]) already exists, just add the entries in the existing section."
|
||||
"state" : "translated",
|
||||
"value" : "Если любой раздел (как [user]) уже существует, просто добавьте новые записи в него."
|
||||
}
|
||||
},
|
||||
"sr" : {
|
||||
@@ -17890,7 +18002,7 @@
|
||||
"ru" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Не разблокировывать"
|
||||
"value" : "Не разблокировать"
|
||||
}
|
||||
},
|
||||
"sr" : {
|
||||
@@ -18074,8 +18186,8 @@
|
||||
},
|
||||
"ru" : {
|
||||
"stringUnit" : {
|
||||
"state" : "new",
|
||||
"value" : "Reveal in Finder"
|
||||
"state" : "translated",
|
||||
"value" : "Открыть в Finder"
|
||||
}
|
||||
},
|
||||
"sr" : {
|
||||
@@ -19369,7 +19481,7 @@
|
||||
},
|
||||
"ru" : {
|
||||
"stringUnit" : {
|
||||
"state" : "new",
|
||||
"state" : "translated",
|
||||
"value" : "Secure Enclave"
|
||||
}
|
||||
},
|
||||
@@ -19734,7 +19846,7 @@
|
||||
"ru" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Это вспомогательное приложение назвается **Secret Agent**, Вы можете наблюдать его в Activity Monitor время от времени."
|
||||
"value" : "Это вспомогательное приложение назвается **Secret Agent**, Вы можете видеть его в Activity Monitor время от времени."
|
||||
}
|
||||
},
|
||||
"sr" : {
|
||||
@@ -20289,7 +20401,7 @@
|
||||
"ru" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Настроить агента"
|
||||
"value" : "Настроить агент"
|
||||
}
|
||||
},
|
||||
"sr" : {
|
||||
@@ -20473,8 +20585,8 @@
|
||||
},
|
||||
"ru" : {
|
||||
"stringUnit" : {
|
||||
"state" : "new",
|
||||
"value" : "Done"
|
||||
"state" : "translated",
|
||||
"value" : "Готово"
|
||||
}
|
||||
},
|
||||
"sr" : {
|
||||
@@ -20658,8 +20770,8 @@
|
||||
},
|
||||
"ru" : {
|
||||
"stringUnit" : {
|
||||
"state" : "new",
|
||||
"value" : "Configure"
|
||||
"state" : "translated",
|
||||
"value" : "Настроить"
|
||||
}
|
||||
},
|
||||
"sr" : {
|
||||
@@ -20843,8 +20955,8 @@
|
||||
},
|
||||
"ru" : {
|
||||
"stringUnit" : {
|
||||
"state" : "new",
|
||||
"value" : "Tell the tools you use how to talk to Secretive."
|
||||
"state" : "translated",
|
||||
"value" : "Настройте Ваши инструменты для работы с Secretive"
|
||||
}
|
||||
},
|
||||
"sr" : {
|
||||
@@ -21028,8 +21140,8 @@
|
||||
},
|
||||
"ru" : {
|
||||
"stringUnit" : {
|
||||
"state" : "new",
|
||||
"value" : "Configure Integrations"
|
||||
"state" : "translated",
|
||||
"value" : "Настроить интеграции"
|
||||
}
|
||||
},
|
||||
"sr" : {
|
||||
@@ -21768,8 +21880,8 @@
|
||||
},
|
||||
"ru" : {
|
||||
"stringUnit" : {
|
||||
"state" : "new",
|
||||
"value" : "Done"
|
||||
"state" : "translated",
|
||||
"value" : "Готово"
|
||||
}
|
||||
},
|
||||
"sr" : {
|
||||
@@ -24735,7 +24847,7 @@
|
||||
},
|
||||
"ru" : {
|
||||
"stringUnit" : {
|
||||
"state" : "new",
|
||||
"state" : "translated",
|
||||
"value" : "Secretive %1$(updateName)@"
|
||||
}
|
||||
},
|
||||
@@ -24920,8 +25032,8 @@
|
||||
},
|
||||
"ru" : {
|
||||
"stringUnit" : {
|
||||
"state" : "new",
|
||||
"value" : "Download Latest Nightly Build"
|
||||
"state" : "translated",
|
||||
"value" : "Скачать последнюю ночную сборку"
|
||||
}
|
||||
},
|
||||
"sr" : {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
/// A release is a representation of a downloadable update.
|
||||
public struct Release: Codable, Sendable {
|
||||
public struct Release: Codable, Sendable, Hashable {
|
||||
|
||||
/// The user-facing name of the release. Typically "Secretive 1.2.3"
|
||||
public let name: String
|
||||
@@ -15,6 +16,8 @@ public struct Release: Codable, Sendable {
|
||||
/// A user-facing description of the contents of the update.
|
||||
public let body: String
|
||||
|
||||
public let attributedBody: AttributedString
|
||||
|
||||
/// Initializes a Release.
|
||||
/// - Parameters:
|
||||
/// - name: The user-facing name of the release.
|
||||
@@ -26,6 +29,56 @@ public struct Release: Codable, Sendable {
|
||||
self.prerelease = prerelease
|
||||
self.html_url = html_url
|
||||
self.body = body
|
||||
self.attributedBody = AttributedString(_markdown: body)
|
||||
}
|
||||
|
||||
public init(_ release: GitHubRelease) {
|
||||
self.name = release.name
|
||||
self.prerelease = release.prerelease
|
||||
self.html_url = release.html_url
|
||||
self.body = release.body
|
||||
self.attributedBody = AttributedString(_markdown: release.body)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public struct GitHubRelease: Codable, Sendable {
|
||||
let name: String
|
||||
let prerelease: Bool
|
||||
let html_url: URL
|
||||
let body: String
|
||||
}
|
||||
|
||||
fileprivate extension AttributedString {
|
||||
|
||||
init(_markdown markdown: String) {
|
||||
let split = markdown.split(whereSeparator: \.isNewline)
|
||||
let lines = split
|
||||
.compactMap {
|
||||
try? AttributedString(markdown: String($0), options: .init(allowsExtendedAttributes: true, interpretedSyntax: .full))
|
||||
}
|
||||
.map { (string: AttributedString) in
|
||||
guard case let .header(level) = string.runs.first?.presentationIntent?.components.first?.kind else { return string }
|
||||
return AttributedString("\n") + string
|
||||
.transformingAttributes(\.font) { font in
|
||||
font.value = switch level {
|
||||
case 2: .headline.bold()
|
||||
case 3: .headline
|
||||
default: .subheadline
|
||||
}
|
||||
}
|
||||
.transformingAttributes(\.underlineStyle) { underline in
|
||||
underline.value = switch level {
|
||||
case 2: .single
|
||||
default: .none
|
||||
}
|
||||
}
|
||||
+ AttributedString("\n")
|
||||
}
|
||||
self = lines.reduce(into: AttributedString()) { partialResult, next in
|
||||
partialResult.append(next)
|
||||
partialResult.append(AttributedString("\n"))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ public final class AnySecretStoreModifiable: AnySecretStore, SecretStoreModifiab
|
||||
private let _create: @Sendable (String, Attributes) async throws -> AnySecret
|
||||
private let _delete: @Sendable (AnySecret) async throws -> Void
|
||||
private let _update: @Sendable (AnySecret, String, Attributes) async throws -> Void
|
||||
private let _supportedKeyTypes: @Sendable () -> [KeyType]
|
||||
private let _supportedKeyTypes: @Sendable () -> KeyAvailability
|
||||
|
||||
public init<SecretStoreType>(_ secretStore: SecretStoreType) where SecretStoreType: SecretStoreModifiable {
|
||||
_create = { AnySecret(try await secretStore.create(name: $0, attributes: $1)) }
|
||||
@@ -87,7 +87,7 @@ public final class AnySecretStoreModifiable: AnySecretStore, SecretStoreModifiab
|
||||
try await _update(secret, name, attributes)
|
||||
}
|
||||
|
||||
public var supportedKeyTypes: [KeyType] {
|
||||
public var supportedKeyTypes: KeyAvailability {
|
||||
_supportedKeyTypes()
|
||||
}
|
||||
|
||||
|
||||
@@ -19,9 +19,10 @@ public final class PublicKeyFileStoreController: Sendable {
|
||||
public func generatePublicKeys(for secrets: [AnySecret], clear: Bool = false) throws {
|
||||
logger.log("Writing public keys to disk")
|
||||
if clear {
|
||||
let validPaths = Set(secrets.map { publicKeyPath(for: $0) }).union(Set(secrets.map { sshCertificatePath(for: $0) }))
|
||||
let validPaths = Set(secrets.map { publicKeyPath(for: $0) })
|
||||
.union(Set(secrets.map { sshCertificatePath(for: $0) }))
|
||||
let contentsOfDirectory = (try? FileManager.default.contentsOfDirectory(atPath: directory.path())) ?? []
|
||||
let fullPathContents = contentsOfDirectory.map { "\(directory)/\($0)" }
|
||||
let fullPathContents = contentsOfDirectory.map { directory.appending(path: $0).path() }
|
||||
|
||||
let untracked = Set(fullPathContents)
|
||||
.subtracting(validPaths)
|
||||
|
||||
@@ -62,10 +62,37 @@ public protocol SecretStoreModifiable<SecretType>: SecretStore {
|
||||
/// - attributes: The new attributes for the secret.
|
||||
func update(secret: SecretType, name: String, attributes: Attributes) async throws
|
||||
|
||||
var supportedKeyTypes: [KeyType] { get }
|
||||
var supportedKeyTypes: KeyAvailability { get }
|
||||
|
||||
}
|
||||
|
||||
public struct KeyAvailability: Sendable {
|
||||
|
||||
public let available: [KeyType]
|
||||
public let unavailable: [UnavailableKeyType]
|
||||
|
||||
public init(available: [KeyType], unavailable: [UnavailableKeyType]) {
|
||||
self.available = available
|
||||
self.unavailable = unavailable
|
||||
}
|
||||
|
||||
public struct UnavailableKeyType: Sendable {
|
||||
public let keyType: KeyType
|
||||
public let reason: Reason
|
||||
|
||||
public init(keyType: KeyType, reason: Reason) {
|
||||
self.keyType = keyType
|
||||
self.reason = reason
|
||||
}
|
||||
|
||||
public enum Reason: Sendable {
|
||||
case macOSUpdateRequired
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
extension NSNotification.Name {
|
||||
|
||||
// Distributed notification that keys were modified out of process (ie, that the management tool added/removed secrets)
|
||||
|
||||
@@ -50,16 +50,16 @@ extension SecureEnclave {
|
||||
let secret = Secret(id: UUID().uuidString, name: name, publicKey: parsed.publicKey.x963Representation, attributes: Attributes(keyType: .init(algorithm: .ecdsa, size: 256), authentication: auth))
|
||||
guard !migratedPublicKeys.contains(parsed.publicKey.x963Representation) else {
|
||||
logger.log("Skipping \(name), public key already present. Marking as migrated.")
|
||||
try markMigrated(secret: secret, oldID: id)
|
||||
markMigrated(secret: secret, oldID: id)
|
||||
continue
|
||||
}
|
||||
logger.log("Migrating \(name).")
|
||||
try store.saveKey(tokenObjectID, name: name, attributes: secret.attributes)
|
||||
logger.log("Migrated \(name).")
|
||||
try markMigrated(secret: secret, oldID: id)
|
||||
markMigrated(secret: secret, oldID: id)
|
||||
migratedAny = true
|
||||
} catch {
|
||||
logger.error("Failed to migrate \(name): \(error).")
|
||||
logger.error("Failed to migrate \(name): \(error.localizedDescription).")
|
||||
}
|
||||
}
|
||||
if migratedAny {
|
||||
@@ -69,10 +69,10 @@ extension SecureEnclave {
|
||||
|
||||
|
||||
|
||||
public func markMigrated(secret: Secret, oldID: Data) throws {
|
||||
public func markMigrated(secret: Secret, oldID: Data) {
|
||||
let updateQuery = KeychainDictionary([
|
||||
kSecClass: kSecClassKey,
|
||||
kSecAttrApplicationLabel: secret.id
|
||||
kSecAttrApplicationLabel: oldID
|
||||
])
|
||||
|
||||
let newID = oldID + Constants.migrationMagicNumber
|
||||
@@ -82,7 +82,7 @@ extension SecureEnclave {
|
||||
|
||||
let status = SecItemUpdate(updateQuery, updatedAttributes)
|
||||
if status != errSecSuccess {
|
||||
throw KeychainError(statusCode: status)
|
||||
logger.warning("Failed to mark \(secret.name) as migrated: \(status).")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -186,17 +186,22 @@ extension SecureEnclave {
|
||||
await reloadSecrets()
|
||||
}
|
||||
|
||||
public var supportedKeyTypes: [KeyType] {
|
||||
if #available(macOS 26, *) {
|
||||
[
|
||||
.ecdsa256,
|
||||
.mldsa65,
|
||||
.mldsa87,
|
||||
]
|
||||
public let supportedKeyTypes: KeyAvailability = {
|
||||
let macOS26Keys: [KeyType] = [.mldsa65, .mldsa87]
|
||||
let isAtLeastMacOS26 = if #available(macOS 26, *) {
|
||||
true
|
||||
} else {
|
||||
[.ecdsa256]
|
||||
false
|
||||
}
|
||||
}
|
||||
return KeyAvailability(
|
||||
available: [
|
||||
.ecdsa256,
|
||||
] + (isAtLeastMacOS26 ? macOS26Keys : []),
|
||||
unavailable: (isAtLeastMacOS26 ? [] : macOS26Keys).map {
|
||||
KeyAvailability.UnavailableKeyType(keyType: $0, reason: .macOSUpdateRequired)
|
||||
}
|
||||
)
|
||||
}()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -34,7 +34,9 @@ public final class XPCServiceDelegate: NSObject, NSXPCListenerDelegate {
|
||||
if let error = error as? Codable & Error {
|
||||
reply(nil, NSError(error))
|
||||
} else {
|
||||
reply(nil, error)
|
||||
// Sending cast directly tries to serialize it and crashes XPCEncoder.
|
||||
let cast = error as NSError
|
||||
reply(nil, NSError(domain: cast.domain, code: cast.code, userInfo: [NSLocalizedDescriptionKey: error.localizedDescription]))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,18 +2,24 @@ import Foundation
|
||||
import SecretAgentKit
|
||||
import Brief
|
||||
import XPCWrappers
|
||||
import OSLog
|
||||
|
||||
/// Delegates all agent input parsing to an XPC service which wraps OpenSSH
|
||||
public final class XPCAgentInputParser: SSHAgentInputParserProtocol {
|
||||
|
||||
private let logger = Logger(subsystem: "com.maxgoedjen.secretive.secretagent", category: "XPCAgentInputParser")
|
||||
private let session: XPCTypedSession<SSHAgent.Request, SSHAgentInputParser.AgentParsingError>
|
||||
|
||||
public init() async throws {
|
||||
logger.debug("Creating XPCAgentInputParser")
|
||||
session = try await XPCTypedSession(serviceName: "com.maxgoedjen.Secretive.SecretAgentInputParser", warmup: true)
|
||||
logger.debug("XPCAgentInputParser is warmed up.")
|
||||
}
|
||||
|
||||
public func parse(data: Data) async throws -> SSHAgent.Request {
|
||||
try await session.send(data)
|
||||
logger.debug("Parsing input")
|
||||
defer { logger.debug("Parsed input") }
|
||||
return try await session.send(data)
|
||||
}
|
||||
|
||||
deinit {
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
5003EF632780081B00DF2006 /* SecureEnclaveSecretKit in Frameworks */ = {isa = PBXBuildFile; productRef = 5003EF622780081B00DF2006 /* SecureEnclaveSecretKit */; };
|
||||
5003EF652780081B00DF2006 /* SmartCardSecretKit in Frameworks */ = {isa = PBXBuildFile; productRef = 5003EF642780081B00DF2006 /* SmartCardSecretKit */; };
|
||||
5008C23E2E525D8900507AC2 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 5008C23D2E525D8200507AC2 /* Localizable.xcstrings */; };
|
||||
5008C2402E52792400507AC2 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 50617D8623FCE48E0099B055 /* Assets.xcassets */; };
|
||||
5008C2412E52D18700507AC2 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 5008C23D2E525D8200507AC2 /* Localizable.xcstrings */; };
|
||||
501421622781262300BBAA70 /* Brief in Frameworks */ = {isa = PBXBuildFile; productRef = 501421612781262300BBAA70 /* Brief */; };
|
||||
501421652781268000BBAA70 /* SecretAgent.app in CopyFiles */ = {isa = PBXBuildFile; fileRef = 50A3B78A24026B7500D209EA /* SecretAgent.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
@@ -36,13 +35,11 @@
|
||||
50571E0524393D1500F76F6C /* LaunchAgentController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50571E0424393D1500F76F6C /* LaunchAgentController.swift */; };
|
||||
50617D8323FCE48E0099B055 /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50617D8223FCE48E0099B055 /* App.swift */; };
|
||||
50617D8523FCE48E0099B055 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50617D8423FCE48E0099B055 /* ContentView.swift */; };
|
||||
50617D8723FCE48E0099B055 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 50617D8623FCE48E0099B055 /* Assets.xcassets */; };
|
||||
50617D8A23FCE48E0099B055 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 50617D8923FCE48E0099B055 /* Preview Assets.xcassets */; };
|
||||
50617DD223FCEFA90099B055 /* PreviewStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50617DD123FCEFA90099B055 /* PreviewStore.swift */; };
|
||||
5065E313295517C500E16645 /* ToolbarButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5065E312295517C500E16645 /* ToolbarButtonStyle.swift */; };
|
||||
5066A6C22516F303004B5A36 /* SetupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5066A6C12516F303004B5A36 /* SetupView.swift */; };
|
||||
5066A6C82516FE6E004B5A36 /* CopyableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5066A6C72516FE6E004B5A36 /* CopyableView.swift */; };
|
||||
506772C72424784600034DED /* Credits.rtf in Resources */ = {isa = PBXBuildFile; fileRef = 506772C62424784600034DED /* Credits.rtf */; };
|
||||
506772C92425BB8500034DED /* NoStoresView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 506772C82425BB8500034DED /* NoStoresView.swift */; };
|
||||
50692D1D2E6FDB880043C7BB /* SecretiveUpdater.xpc in Embed XPC Services */ = {isa = PBXBuildFile; fileRef = 50692D122E6FDB880043C7BB /* SecretiveUpdater.xpc */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
50692D282E6FDB8D0043C7BB /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50692D242E6FDB8D0043C7BB /* main.swift */; };
|
||||
@@ -73,6 +70,10 @@
|
||||
50BDCB762E6450950072D2E7 /* ConfigurationItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50BDCB752E6450950072D2E7 /* ConfigurationItemView.swift */; };
|
||||
50C385A52407A76D00AF2719 /* SecretDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50C385A42407A76D00AF2719 /* SecretDetailView.swift */; };
|
||||
50CF4ABC2E601B0F005588DC /* ActionButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50CF4ABB2E601B0F005588DC /* ActionButtonStyle.swift */; };
|
||||
50E4C4532E73C78C00C73783 /* WindowBackgroundStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50E4C4522E73C78900C73783 /* WindowBackgroundStyle.swift */; };
|
||||
50E4C4C32E7765DF00C73783 /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50E4C4C22E7765DF00C73783 /* AboutView.swift */; };
|
||||
50E4C4C82E777E4200C73783 /* AppIcon.icon in Resources */ = {isa = PBXBuildFile; fileRef = 50E4C4C72E777E4200C73783 /* AppIcon.icon */; };
|
||||
50E4C4C92E777E4200C73783 /* AppIcon.icon in Resources */ = {isa = PBXBuildFile; fileRef = 50E4C4C72E777E4200C73783 /* AppIcon.icon */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@@ -197,7 +198,6 @@
|
||||
50617D7F23FCE48E0099B055 /* Secretive.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Secretive.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
50617D8223FCE48E0099B055 /* App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App.swift; sourceTree = "<group>"; };
|
||||
50617D8423FCE48E0099B055 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
|
||||
50617D8623FCE48E0099B055 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
50617D8923FCE48E0099B055 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
|
||||
50617D8E23FCE48E0099B055 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
50617D8F23FCE48E0099B055 /* Secretive.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Secretive.entitlements; sourceTree = "<group>"; };
|
||||
@@ -205,7 +205,6 @@
|
||||
5065E312295517C500E16645 /* ToolbarButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToolbarButtonStyle.swift; sourceTree = "<group>"; };
|
||||
5066A6C12516F303004B5A36 /* SetupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetupView.swift; sourceTree = "<group>"; };
|
||||
5066A6C72516FE6E004B5A36 /* CopyableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CopyableView.swift; sourceTree = "<group>"; };
|
||||
506772C62424784600034DED /* Credits.rtf */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; path = Credits.rtf; sourceTree = "<group>"; };
|
||||
506772C82425BB8500034DED /* NoStoresView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoStoresView.swift; sourceTree = "<group>"; };
|
||||
50692BA52E6D5CC90043C7BB /* InternetAccessPolicy.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = InternetAccessPolicy.plist; sourceTree = "<group>"; };
|
||||
50692D122E6FDB880043C7BB /* SecretiveUpdater.xpc */ = {isa = PBXFileReference; explicitFileType = "wrapper.xpc-service"; includeInIndex = 0; path = SecretiveUpdater.xpc; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
@@ -239,6 +238,9 @@
|
||||
50BDCB752E6450950072D2E7 /* ConfigurationItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationItemView.swift; sourceTree = "<group>"; };
|
||||
50C385A42407A76D00AF2719 /* SecretDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecretDetailView.swift; sourceTree = "<group>"; };
|
||||
50CF4ABB2E601B0F005588DC /* ActionButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionButtonStyle.swift; sourceTree = "<group>"; };
|
||||
50E4C4522E73C78900C73783 /* WindowBackgroundStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowBackgroundStyle.swift; sourceTree = "<group>"; };
|
||||
50E4C4C22E7765DF00C73783 /* AboutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutView.swift; sourceTree = "<group>"; };
|
||||
50E4C4C72E777E4200C73783 /* AppIcon.icon */ = {isa = PBXFileReference; lastKnownFileType = folder.iconcomposer.icon; path = AppIcon.icon; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@@ -293,15 +295,16 @@
|
||||
path = Helpers;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
504788ED2E681EB200B4556F /* Styles */ = {
|
||||
504788ED2E681EB200B4556F /* Modifiers */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
50E4C4522E73C78900C73783 /* WindowBackgroundStyle.swift */,
|
||||
50CF4ABB2E601B0F005588DC /* ActionButtonStyle.swift */,
|
||||
50BDCB732E6436C60072D2E7 /* ErrorStyle.swift */,
|
||||
504789222E697DD300B4556F /* BoxBackgroundStyle.swift */,
|
||||
5065E312295517C500E16645 /* ToolbarButtonStyle.swift */,
|
||||
);
|
||||
path = Styles;
|
||||
path = Modifiers;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
504788EE2E681EC300B4556F /* Secrets */ = {
|
||||
@@ -335,6 +338,7 @@
|
||||
504788F02E681F0100B4556F /* Views */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
50E4C4C22E7765DF00C73783 /* AboutView.swift */,
|
||||
50BDCB712E63BAF20072D2E7 /* AgentStatusView.swift */,
|
||||
50617D8423FCE48E0099B055 /* ContentView.swift */,
|
||||
5066A6C72516FE6E004B5A36 /* CopyableView.swift */,
|
||||
@@ -375,11 +379,10 @@
|
||||
508A58B0241ED1C40069DC07 /* Views */,
|
||||
508A58B1241ED1EA0069DC07 /* Controllers */,
|
||||
50033AC427813F1C00253856 /* Helpers */,
|
||||
50617D8623FCE48E0099B055 /* Assets.xcassets */,
|
||||
50617D8E23FCE48E0099B055 /* Info.plist */,
|
||||
508BF28D25B4F005009EFB7E /* InternetAccessPolicy.plist */,
|
||||
50E4C4C72E777E4200C73783 /* AppIcon.icon */,
|
||||
50617D8F23FCE48E0099B055 /* Secretive.entitlements */,
|
||||
506772C62424784600034DED /* Credits.rtf */,
|
||||
5008C23D2E525D8200507AC2 /* Localizable.xcstrings */,
|
||||
50617D8823FCE48E0099B055 /* Preview Content */,
|
||||
);
|
||||
@@ -432,7 +435,7 @@
|
||||
children = (
|
||||
504788EF2E681ED700B4556F /* Configuration */,
|
||||
504788EE2E681EC300B4556F /* Secrets */,
|
||||
504788ED2E681EB200B4556F /* Styles */,
|
||||
504788ED2E681EB200B4556F /* Modifiers */,
|
||||
504788F02E681F0100B4556F /* Views */,
|
||||
);
|
||||
path = Views;
|
||||
@@ -643,9 +646,8 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
50617D8A23FCE48E0099B055 /* Preview Assets.xcassets in Resources */,
|
||||
50E4C4C82E777E4200C73783 /* AppIcon.icon in Resources */,
|
||||
5008C23E2E525D8900507AC2 /* Localizable.xcstrings in Resources */,
|
||||
50617D8723FCE48E0099B055 /* Assets.xcassets in Resources */,
|
||||
506772C72424784600034DED /* Credits.rtf in Resources */,
|
||||
508BF28E25B4F005009EFB7E /* InternetAccessPolicy.plist in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -671,8 +673,8 @@
|
||||
50A3B79724026B7600D209EA /* Main.storyboard in Resources */,
|
||||
5008C2412E52D18700507AC2 /* Localizable.xcstrings in Resources */,
|
||||
50A3B79424026B7600D209EA /* Preview Assets.xcassets in Resources */,
|
||||
50E4C4C92E777E4200C73783 /* AppIcon.icon in Resources */,
|
||||
508BF2AA25B4F1CB009EFB7E /* InternetAccessPolicy.plist in Resources */,
|
||||
5008C2402E52792400507AC2 /* Assets.xcassets in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -685,7 +687,9 @@
|
||||
files = (
|
||||
504788F22E681F3A00B4556F /* Instructions.swift in Sources */,
|
||||
50BDCB742E6436CA0072D2E7 /* ErrorStyle.swift in Sources */,
|
||||
50E4C4C32E7765DF00C73783 /* AboutView.swift in Sources */,
|
||||
2C4A9D2F2636FFD3008CC8E2 /* EditSecretView.swift in Sources */,
|
||||
50E4C4532E73C78C00C73783 /* WindowBackgroundStyle.swift in Sources */,
|
||||
5091D2BC25183B830049FD9B /* ApplicationDirectoryController.swift in Sources */,
|
||||
504788EC2E680DC800B4556F /* URLs.swift in Sources */,
|
||||
504789232E697DD300B4556F /* BoxBackgroundStyle.swift in Sources */,
|
||||
@@ -1045,7 +1049,7 @@
|
||||
INFOPLIST_KEY_CFBundleDisplayName = SecretiveUpdater;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Max Goedjen. All rights reserved.";
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 26.0;
|
||||
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretiveUpdater;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
@@ -1085,7 +1089,7 @@
|
||||
INFOPLIST_KEY_CFBundleDisplayName = SecretiveUpdater;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Max Goedjen. All rights reserved.";
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 26.0;
|
||||
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretiveUpdater;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
@@ -1127,7 +1131,7 @@
|
||||
INFOPLIST_KEY_CFBundleDisplayName = SecretiveUpdater;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Max Goedjen. All rights reserved.";
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 26.0;
|
||||
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretiveUpdater;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
@@ -1159,7 +1163,7 @@
|
||||
INFOPLIST_KEY_CFBundleDisplayName = SecretAgentInputParser;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Max Goedjen. All rights reserved.";
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 26.0;
|
||||
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretAgentInputParser;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
@@ -1189,7 +1193,7 @@
|
||||
INFOPLIST_KEY_CFBundleDisplayName = SecretAgentInputParser;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Max Goedjen. All rights reserved.";
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 26.0;
|
||||
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretAgentInputParser;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
@@ -1221,7 +1225,7 @@
|
||||
INFOPLIST_KEY_CFBundleDisplayName = SecretAgentInputParser;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Max Goedjen. All rights reserved.";
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 26.0;
|
||||
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretAgentInputParser;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
|
||||
@@ -4,55 +4,18 @@ import SecureEnclaveSecretKit
|
||||
import SmartCardSecretKit
|
||||
import Brief
|
||||
|
||||
extension EnvironmentValues {
|
||||
|
||||
// This is injected through .environment modifier below instead of @Entry for performance reasons (basially, restrictions around init/mainactor causing delay in loading secrets/"empty screen" blip).
|
||||
@MainActor fileprivate static let _secretStoreList: SecretStoreList = {
|
||||
let list = SecretStoreList()
|
||||
let cryptoKit = SecureEnclave.Store()
|
||||
let migrator = SecureEnclave.CryptoKitMigrator()
|
||||
try? migrator.migrate(to: cryptoKit)
|
||||
list.add(store: cryptoKit)
|
||||
list.add(store: SmartCard.Store())
|
||||
return list
|
||||
}()
|
||||
|
||||
private static let _agentStatusChecker = AgentStatusChecker()
|
||||
@Entry var agentStatusChecker: any AgentStatusCheckerProtocol = _agentStatusChecker
|
||||
private static let _updater: any UpdaterProtocol = {
|
||||
@AppStorage("defaultsHasRunSetup") var hasRunSetup = false
|
||||
return Updater(checkOnLaunch: hasRunSetup)
|
||||
}()
|
||||
@Entry var updater: any UpdaterProtocol = _updater
|
||||
|
||||
private static let _justUpdatedChecker = JustUpdatedChecker()
|
||||
@Entry var justUpdatedChecker: any JustUpdatedCheckerProtocol = _justUpdatedChecker
|
||||
|
||||
@MainActor var secretStoreList: SecretStoreList {
|
||||
EnvironmentValues._secretStoreList
|
||||
}
|
||||
}
|
||||
|
||||
@main
|
||||
struct Secretive: App {
|
||||
|
||||
@Environment(\.agentStatusChecker) var agentStatusChecker
|
||||
@Environment(\.justUpdatedChecker) var justUpdatedChecker
|
||||
@AppStorage("defaultsHasRunSetup") var hasRunSetup = false
|
||||
@State private var showingSetup = false
|
||||
@State private var showingIntegrations = false
|
||||
@State private var showingCreation = false
|
||||
|
||||
@SceneBuilder var body: some Scene {
|
||||
WindowGroup {
|
||||
ContentView(showingCreation: $showingCreation, runningSetup: $showingSetup, hasRunSetup: $hasRunSetup)
|
||||
ContentView()
|
||||
.environment(EnvironmentValues._secretStoreList)
|
||||
.onAppear {
|
||||
if !hasRunSetup {
|
||||
showingSetup = true
|
||||
}
|
||||
}
|
||||
.onReceive(NotificationCenter.default.publisher(for: NSApplication.didBecomeActiveNotification)) { _ in
|
||||
@AppStorage("defaultsHasRunSetup") var hasRunSetup = false
|
||||
guard hasRunSetup else { return }
|
||||
agentStatusChecker.check()
|
||||
if agentStatusChecker.running && justUpdatedChecker.justUpdatedBuild {
|
||||
@@ -62,25 +25,52 @@ struct Secretive: App {
|
||||
forceLaunchAgent()
|
||||
}
|
||||
}
|
||||
.sheet(isPresented: $showingIntegrations) {
|
||||
IntegrationsView()
|
||||
}
|
||||
}
|
||||
.commands {
|
||||
AppCommands()
|
||||
}
|
||||
WindowGroup(id: String(describing: IntegrationsView.self)) {
|
||||
IntegrationsView()
|
||||
}
|
||||
.windowResizability(.contentMinSize)
|
||||
WindowGroup(id: String(describing: AboutView.self)) {
|
||||
AboutView()
|
||||
}
|
||||
.windowStyle(.hiddenTitleBar)
|
||||
.windowResizability(.contentSize)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension Secretive {
|
||||
|
||||
struct AppCommands: Commands {
|
||||
|
||||
@Environment(\.openWindow) var openWindow
|
||||
@Environment(\.openURL) var openURL
|
||||
@FocusedValue(\.showCreateSecret) var showCreateSecret
|
||||
|
||||
var body: some Commands {
|
||||
CommandGroup(replacing: .appInfo) {
|
||||
Button(.aboutMenuBarTitle, systemImage: "info.circle") {
|
||||
openWindow(id: String(describing: AboutView.self))
|
||||
}
|
||||
}
|
||||
CommandGroup(before: CommandGroupPlacement.appSettings) {
|
||||
Button(.integrationsMenuBarTitle, systemImage: "app.connected.to.app.below.fill") {
|
||||
showingIntegrations = true
|
||||
openWindow(id: String(describing: IntegrationsView.self))
|
||||
}
|
||||
}
|
||||
CommandGroup(after: CommandGroupPlacement.newItem) {
|
||||
Button(.appMenuNewSecretButton) {
|
||||
showingCreation = true
|
||||
Button(.appMenuNewSecretButton, systemImage: "plus") {
|
||||
showCreateSecret?()
|
||||
}
|
||||
.keyboardShortcut(KeyboardShortcut(KeyEquivalent("N"), modifiers: [.command, .shift]))
|
||||
.disabled(showCreateSecret?.isEnabled == false)
|
||||
}
|
||||
CommandGroup(replacing: .help) {
|
||||
Button(.appMenuHelpButton) {
|
||||
NSWorkspace.shared.open(Constants.helpURL)
|
||||
openURL(Constants.helpURL)
|
||||
}
|
||||
}
|
||||
SidebarCommands()
|
||||
@@ -113,8 +103,56 @@ extension Secretive {
|
||||
|
||||
}
|
||||
|
||||
|
||||
private enum Constants {
|
||||
static let helpURL = URL(string: "https://github.com/maxgoedjen/secretive/blob/main/FAQ.md")!
|
||||
}
|
||||
|
||||
|
||||
extension EnvironmentValues {
|
||||
|
||||
// This is injected through .environment modifier below instead of @Entry for performance reasons (basially, restrictions around init/mainactor causing delay in loading secrets/"empty screen" blip).
|
||||
@MainActor fileprivate static let _secretStoreList: SecretStoreList = {
|
||||
let list = SecretStoreList()
|
||||
let cryptoKit = SecureEnclave.Store()
|
||||
let migrator = SecureEnclave.CryptoKitMigrator()
|
||||
try? migrator.migrate(to: cryptoKit)
|
||||
list.add(store: cryptoKit)
|
||||
list.add(store: SmartCard.Store())
|
||||
return list
|
||||
}()
|
||||
|
||||
private static let _agentStatusChecker = AgentStatusChecker()
|
||||
@Entry var agentStatusChecker: any AgentStatusCheckerProtocol = _agentStatusChecker
|
||||
private static let _updater: any UpdaterProtocol = {
|
||||
@AppStorage("defaultsHasRunSetup") var hasRunSetup = false
|
||||
return Updater(checkOnLaunch: hasRunSetup)
|
||||
}()
|
||||
@Entry var updater: any UpdaterProtocol = _updater
|
||||
|
||||
private static let _justUpdatedChecker = JustUpdatedChecker()
|
||||
@Entry var justUpdatedChecker: any JustUpdatedCheckerProtocol = _justUpdatedChecker
|
||||
|
||||
@MainActor var secretStoreList: SecretStoreList {
|
||||
EnvironmentValues._secretStoreList
|
||||
}
|
||||
}
|
||||
|
||||
extension FocusedValues {
|
||||
@Entry var showCreateSecret: OpenSheet?
|
||||
}
|
||||
|
||||
final class OpenSheet {
|
||||
|
||||
let closure: () -> Void
|
||||
let isEnabled: Bool
|
||||
|
||||
init(isEnabled: Bool = true, closure: @escaping () -> Void) {
|
||||
self.isEnabled = isEnabled
|
||||
self.closure = closure
|
||||
}
|
||||
|
||||
func callAsFunction() {
|
||||
closure()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 43 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 69 KiB |
@@ -1,68 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "Icon-macOS-ClearDark-16x16@1x.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "16x16"
|
||||
},
|
||||
{
|
||||
"filename" : "Icon-macOS-ClearDark-16x16@2x.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "16x16"
|
||||
},
|
||||
{
|
||||
"filename" : "Icon-macOS-ClearDark-32x32@1x.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "32x32"
|
||||
},
|
||||
{
|
||||
"filename" : "Icon-macOS-ClearDark-32x32@2x.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "32x32"
|
||||
},
|
||||
{
|
||||
"filename" : "Icon-macOS-ClearDark-128x128@1x.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "128x128"
|
||||
},
|
||||
{
|
||||
"filename" : "Icon-macOS-ClearDark-128x128@2x.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "128x128"
|
||||
},
|
||||
{
|
||||
"filename" : "Icon-macOS-ClearDark-256x256@1x.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "256x256"
|
||||
},
|
||||
{
|
||||
"filename" : "Icon-macOS-ClearDark-256x256@2x.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "256x256"
|
||||
},
|
||||
{
|
||||
"filename" : "Icon-macOS-ClearDark-512x512@1x.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "512x512"
|
||||
},
|
||||
{
|
||||
"filename" : "Icon-macOS-ClearDark-1024x1024@1x.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "512x512"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 1.0 MiB |
|
Before Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 108 KiB |
|
Before Width: | Height: | Size: 856 B |
|
Before Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 108 KiB |
|
Before Width: | Height: | Size: 356 KiB |
|
Before Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 356 KiB |
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
{\rtf1\ansi\ansicpg1252\cocoartf2580
|
||||
\cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
|
||||
{\colortbl;\red255\green255\blue255;}
|
||||
{\*\expandedcolortbl;;}
|
||||
\margl1440\margr1440\vieww9000\viewh8400\viewkind0
|
||||
\pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6119\tx6480\tx7200\tx7920\tx8640\pardirnatural\partightenfactor0
|
||||
{\field{\*\fldinst{HYPERLINK "https://github.com/maxgoedjen/secretive"}}{\fldrslt
|
||||
\f0\fs24 \cf0 GitHub Repository}}
|
||||
\f0\fs24 \
|
||||
\pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\pardirnatural\partightenfactor0
|
||||
\cf0 \
|
||||
{\field{\*\fldinst{HYPERLINK "GITHUB_BUILD_URL"}}{\fldrslt Build Log}}\
|
||||
\
|
||||
Special Thanks To:\
|
||||
\
|
||||
{\field{\*\fldinst{HYPERLINK "https://github.com/maxgoedjen/secretive/graphs/contributors"}}{\fldrslt Contributors}}:\
|
||||
{\field{\*\fldinst{HYPERLINK "https://github.com/0xflotus"}}{\fldrslt 0xflotus}}\
|
||||
{\field{\*\fldinst{HYPERLINK "https://github.com/aaron-trout"}}{\fldrslt Aaron Trout}}\
|
||||
\pard\pardeftab720\partightenfactor0
|
||||
{\field{\*\fldinst{HYPERLINK "https://github.com/EppO"}}{\fldrslt \cf0 Florent Monbillard}}\
|
||||
{\field{\*\fldinst{HYPERLINK "https://github.com/vladimyr"}}{\fldrslt Dario Vladovi\uc0\u263 }}\
|
||||
{\field{\*\fldinst{HYPERLINK "https://github.com/lavalleeale"}}{\fldrslt Alex Lavallee}}\
|
||||
{\field{\*\fldinst{HYPERLINK "https://github.com/joshheyse"}}{\fldrslt Josh}}\
|
||||
{\field{\*\fldinst{HYPERLINK "https://github.com/diesal11"}}{\fldrslt Dylan Lundy}}\
|
||||
\
|
||||
\pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\pardirnatural\partightenfactor0
|
||||
\cf0 Testers:\
|
||||
{\field{\*\fldinst{HYPERLINK "https://github.com/bdash"}}{\fldrslt Mark Rowe}}\
|
||||
{\field{\*\fldinst{HYPERLINK "https://github.com/danielctull"}}{\fldrslt Daniel Tull}}\
|
||||
{\field{\*\fldinst{HYPERLINK "https://github.com/davedelong"}}{\fldrslt Dave DeLong}}\
|
||||
{\field{\*\fldinst{HYPERLINK "https://github.com/esttorhe"}}{\fldrslt Esteban Torres}}\
|
||||
{\field{\*\fldinst{HYPERLINK "https://github.com/joeblau"}}{\fldrslt Joe Blau}}\
|
||||
{\field{\*\fldinst{HYPERLINK "https://github.com/marksands"}}{\fldrslt Mark Sands}}\
|
||||
{\field{\*\fldinst{HYPERLINK "https://github.com/mergesort"}}{\fldrslt Joe Fabisevich}}\
|
||||
{\field{\*\fldinst{HYPERLINK "https://github.com/phillco"}}{\fldrslt Phil Cohen}}\
|
||||
{\field{\*\fldinst{HYPERLINK "https://github.com/zackdotcomputer"}}{\fldrslt Zack Sheppard}}}
|
||||
@@ -24,6 +24,8 @@
|
||||
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>$(PRODUCT_NAME) is MIT Licensed.</string>
|
||||
<key>GitHubBuildLog</key>
|
||||
<string>$(CI_BUILD_LINK)</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string>NSApplication</string>
|
||||
<key>NSSupportsAutomaticTermination</key>
|
||||
|
||||
@@ -60,16 +60,17 @@ extension Preview {
|
||||
let id = UUID()
|
||||
var name: String { "Modifiable Preview Store" }
|
||||
let secrets: [Secret]
|
||||
var supportedKeyTypes: [KeyType] {
|
||||
if #available(macOS 26, *) {
|
||||
[
|
||||
var supportedKeyTypes: KeyAvailability {
|
||||
return KeyAvailability(
|
||||
available: [
|
||||
.ecdsa256,
|
||||
.mldsa65,
|
||||
.mldsa87,
|
||||
.mldsa87
|
||||
],
|
||||
unavailable: [
|
||||
.init(keyType: .ecdsa384, reason: .macOSUpdateRequired)
|
||||
]
|
||||
} else {
|
||||
[.ecdsa256]
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
init(secrets: [Secret]) {
|
||||
|
||||
@@ -32,7 +32,7 @@ struct ConfigurationItemView<Content: View>: View {
|
||||
Spacer()
|
||||
switch action {
|
||||
case .copy(let string):
|
||||
Button(.copyableClickToCopyButton, systemImage: "document.on.document") {
|
||||
Button(.copyableClickToCopyButton, systemImage: "doc.on.doc") {
|
||||
NSPasteboard.general.declareTypes([.string], owner: nil)
|
||||
NSPasteboard.general.setString(string, forType: .string)
|
||||
}
|
||||
|
||||
@@ -21,47 +21,19 @@ struct IntegrationsView: View {
|
||||
}
|
||||
}
|
||||
} detail: {
|
||||
IntegrationsDetailView(selectedInstruction: $selectedInstruction)
|
||||
.fauxToolbar {
|
||||
Button(.setupDoneButton) {
|
||||
dismiss()
|
||||
}
|
||||
.normalButton()
|
||||
}
|
||||
IntegrationsDetailView(selectedInstruction: $selectedInstruction)
|
||||
}
|
||||
.toolbar {
|
||||
Button(.setupDoneButton) {
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
.hiddenToolbar()
|
||||
.windowBackgroundStyle(.thinMaterial)
|
||||
.onAppear {
|
||||
selectedInstruction = instructions.gettingStarted
|
||||
}
|
||||
.frame(minHeight: 500)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension View {
|
||||
|
||||
func fauxToolbar<Content: View>(content: () -> Content) -> some View {
|
||||
modifier(FauxToolbarModifier(toolbarContent: content()))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
struct FauxToolbarModifier<ToolbarContent: View>: ViewModifier {
|
||||
|
||||
var toolbarContent: ToolbarContent
|
||||
|
||||
func body(content: Content) -> some View {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
content
|
||||
Divider()
|
||||
HStack {
|
||||
Spacer()
|
||||
toolbarContent
|
||||
.padding(.top, 8)
|
||||
.padding(.trailing, 16)
|
||||
.padding(.bottom, 16)
|
||||
}
|
||||
}
|
||||
|
||||
.frame(minWidth: 400, minHeight: 400)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -85,7 +85,10 @@ struct SetupView: View {
|
||||
integrations = true
|
||||
}, content: {
|
||||
IntegrationsView()
|
||||
.frame(minWidth: 500, minHeight: 400)
|
||||
})
|
||||
.frame(idealWidth: 600)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,10 +175,13 @@ struct StepView<Content: View>: View {
|
||||
.frame(width: 20)
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text(title)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.bold()
|
||||
Text(description)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
if let detail {
|
||||
Text(detail)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.font(.callout)
|
||||
.italic()
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ struct ToolConfigurationView: View {
|
||||
selectedSecret = created
|
||||
}
|
||||
}
|
||||
.fixedSize()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ extension View {
|
||||
|
||||
}
|
||||
|
||||
struct MenuButtonModifier: ViewModifier {
|
||||
struct ToolbarCircleButtonModifier: ViewModifier {
|
||||
|
||||
func body(content: Content) -> some View {
|
||||
if #available(macOS 26.0, *) {
|
||||
@@ -40,8 +40,8 @@ struct MenuButtonModifier: ViewModifier {
|
||||
|
||||
extension View {
|
||||
|
||||
func menuButton() -> some View {
|
||||
modifier(MenuButtonModifier())
|
||||
func toolbarCircleButton() -> some View {
|
||||
modifier(ToolbarCircleButtonModifier())
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import SwiftUI
|
||||
|
||||
struct ToolbarButtonStyle: ButtonStyle {
|
||||
struct ToolbarStatusButtonStyle: ButtonStyle {
|
||||
|
||||
private let lightColor: Color
|
||||
private let darkColor: Color
|
||||
@@ -39,6 +39,7 @@ struct ToolbarButtonStyle: ButtonStyle {
|
||||
} else {
|
||||
configuration
|
||||
.label
|
||||
.padding(EdgeInsets(top: 6, leading: 8, bottom: 6, trailing: 8))
|
||||
.background(colorScheme == .light ? lightColor : darkColor)
|
||||
.foregroundColor(.white)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 5))
|
||||
@@ -55,3 +56,24 @@ struct ToolbarButtonStyle: ButtonStyle {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ToolbarButtonStyle: PrimitiveButtonStyle {
|
||||
|
||||
var tint: Color = .white.opacity(0.1)
|
||||
|
||||
func makeBody(configuration: Configuration) -> some View {
|
||||
if #available(macOS 26.0, *) {
|
||||
configuration
|
||||
.label
|
||||
.padding(.vertical, 10)
|
||||
.padding(.horizontal, 12)
|
||||
.glassEffect(.regular.interactive().tint(tint))
|
||||
} else {
|
||||
BorderedButtonStyle().makeBody(configuration: configuration)
|
||||
.padding(EdgeInsets(top: 6, leading: 8, bottom: 6, trailing: 8))
|
||||
.foregroundColor(.white)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 5))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import SwiftUI
|
||||
|
||||
struct WindowBackgroundStyleModifier: ViewModifier {
|
||||
|
||||
let shapeStyle: any ShapeStyle
|
||||
|
||||
func body(content: Content) -> some View {
|
||||
if #available(macOS 15.0, *) {
|
||||
content
|
||||
.containerBackground(
|
||||
shapeStyle, for: .window
|
||||
)
|
||||
} else {
|
||||
content
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension View {
|
||||
|
||||
func windowBackgroundStyle(_ style: some ShapeStyle) -> some View {
|
||||
modifier(WindowBackgroundStyleModifier(shapeStyle: style))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
struct HiddenToolbarModifier: ViewModifier {
|
||||
|
||||
func body(content: Content) -> some View {
|
||||
if #available(macOS 15.0, *) {
|
||||
content
|
||||
.toolbarBackgroundVisibility(.hidden, for: .automatic)
|
||||
} else {
|
||||
content
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension View {
|
||||
|
||||
func hiddenToolbar() -> some View {
|
||||
modifier(HiddenToolbarModifier())
|
||||
}
|
||||
|
||||
}
|
||||
@@ -75,10 +75,23 @@ struct CreateSecretView<StoreType: SecretStoreModifiable>: View {
|
||||
Section {
|
||||
VStack {
|
||||
Picker(.createSecretKeyTypeLabel, selection: $keyType) {
|
||||
ForEach(store.supportedKeyTypes, id: \.self) { option in
|
||||
ForEach(store.supportedKeyTypes.available, id: \.self) { option in
|
||||
Text(String(describing: option))
|
||||
.tag(option)
|
||||
.font(.caption)
|
||||
}
|
||||
Divider()
|
||||
ForEach(store.supportedKeyTypes.unavailable, id: \.keyType) { option in
|
||||
VStack {
|
||||
Button {
|
||||
} label: {
|
||||
Text(String(describing: option.keyType))
|
||||
switch option.reason {
|
||||
case .macOSUpdateRequired:
|
||||
Text(.createSecretKeyTypeMacOSUpdateRequiredLabel)
|
||||
}
|
||||
}
|
||||
}
|
||||
.selectionDisabled()
|
||||
}
|
||||
}
|
||||
if keyType?.algorithm == .mldsa {
|
||||
@@ -119,7 +132,7 @@ struct CreateSecretView<StoreType: SecretStoreModifiable>: View {
|
||||
.padding()
|
||||
}
|
||||
.onAppear {
|
||||
keyType = store.supportedKeyTypes.first
|
||||
keyType = store.supportedKeyTypes.available.first
|
||||
}
|
||||
.formStyle(.grouped)
|
||||
}
|
||||
|
||||
69
Sources/Secretive/Views/Views/AboutView.swift
Normal file
@@ -0,0 +1,69 @@
|
||||
import SwiftUI
|
||||
|
||||
struct AboutView: View {
|
||||
var body: some View {
|
||||
if #available(macOS 15.0, *) {
|
||||
AboutViewContent()
|
||||
.containerBackground(
|
||||
.thinMaterial, for: .window
|
||||
)
|
||||
} else {
|
||||
AboutViewContent()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct AboutViewContent: View {
|
||||
|
||||
@Environment(\.openURL) var openURL
|
||||
var body: some View {
|
||||
VStack(spacing: 10) {
|
||||
HStack {
|
||||
Image(nsImage: NSApplication.shared.applicationIconImage)
|
||||
VStack(alignment: .leading) {
|
||||
Text(verbatim: "Secretive")
|
||||
.font(.system(.largeTitle, weight: .bold))
|
||||
Text("**\(Bundle.main.versionNumber)** (\(Bundle.main.buildNumber))")
|
||||
.fixedSize(horizontal: true, vertical: false)
|
||||
HStack {
|
||||
Button(.aboutViewOnGithubButton) {
|
||||
openURL(URL(string: "https://github.com/maxgoedjen/secretive")!)
|
||||
}
|
||||
.normalButton()
|
||||
Button(.aboutBuildLogButton) {
|
||||
openURL(Bundle.main.buildLog)
|
||||
}
|
||||
.normalButton()
|
||||
}
|
||||
}
|
||||
}
|
||||
Text(.aboutThanks(contributorsLink: "https://github.com/maxgoedjen/secretive/graphs/contributors", sponsorsLink: "https://github.com/sponsors/maxgoedjen"))
|
||||
.font(.headline)
|
||||
Text(.aboutOpenSourceNotice)
|
||||
.font(.subheadline)
|
||||
}
|
||||
.padding(EdgeInsets(top: 10, leading: 30, bottom: 30, trailing: 30))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private extension Bundle {
|
||||
|
||||
var buildLog: URL {
|
||||
URL(string: infoDictionary!["GitHubBuildLog"] as! String)!
|
||||
}
|
||||
|
||||
var versionNumber: String {
|
||||
infoDictionary?["CFBundleShortVersionString"] as? String ?? "0.0.0"
|
||||
}
|
||||
|
||||
var buildNumber: String {
|
||||
infoDictionary?["CFBundleVersion"] as? String ?? "0.0"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#Preview {
|
||||
AboutView()
|
||||
.frame(width: 500, height: 250)
|
||||
}
|
||||
@@ -6,19 +6,21 @@ import Brief
|
||||
|
||||
struct ContentView: View {
|
||||
|
||||
@Binding var showingCreation: Bool
|
||||
@Binding var runningSetup: Bool
|
||||
@Binding var hasRunSetup: Bool
|
||||
@State var showingAgentInfo = false
|
||||
@State var activeSecret: AnySecret?
|
||||
@Environment(\.colorScheme) var colorScheme
|
||||
|
||||
@Environment(\.secretStoreList) private var storeList
|
||||
@Environment(\.updater) private var updater: any UpdaterProtocol
|
||||
@Environment(\.agentStatusChecker) private var agentStatusChecker: any AgentStatusCheckerProtocol
|
||||
|
||||
@State private var selectedUpdate: Release?
|
||||
|
||||
@Environment(\.colorScheme) private var colorScheme
|
||||
@Environment(\.openWindow) private var openWindow
|
||||
@Environment(\.secretStoreList) private var storeList
|
||||
@Environment(\.updater) private var updater
|
||||
@Environment(\.agentStatusChecker) private var agentStatusChecker
|
||||
|
||||
@AppStorage("defaultsHasRunSetup") private var hasRunSetup = false
|
||||
@State private var showingCreation = false
|
||||
@State private var showingAppPathNotice = false
|
||||
@State private var runningSetup = false
|
||||
@State private var showingAgentInfo = false
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
@@ -35,6 +37,23 @@ struct ContentView: View {
|
||||
toolbarItem(appPathNoticeView, id: "appPath")
|
||||
toolbarItem(newItemView, id: "new")
|
||||
}
|
||||
.onAppear {
|
||||
if !hasRunSetup {
|
||||
runningSetup = true
|
||||
}
|
||||
}
|
||||
.focusedSceneValue(\.showCreateSecret, .init(isEnabled: !runningSetup) {
|
||||
showingCreation = true
|
||||
})
|
||||
.sheet(isPresented: $showingCreation) {
|
||||
if let modifiable = storeList.modifiableStore {
|
||||
CreateSecretView(store: modifiable) { created in
|
||||
if let created {
|
||||
activeSecret = created
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.sheet(isPresented: $runningSetup) {
|
||||
SetupView(setupComplete: $hasRunSetup)
|
||||
}
|
||||
@@ -85,24 +104,9 @@ extension ContentView {
|
||||
.font(.headline)
|
||||
.foregroundColor(.white)
|
||||
})
|
||||
.buttonStyle(ToolbarButtonStyle(color: color))
|
||||
.buttonStyle(ToolbarStatusButtonStyle(color: color))
|
||||
.sheet(item: $selectedUpdate) { update in
|
||||
VStack {
|
||||
if updater.currentVersion.isTestBuild {
|
||||
VStack {
|
||||
if let description = updater.currentVersion.previewDescription {
|
||||
Text(description)
|
||||
}
|
||||
Link(destination: URL(string: "https://github.com/maxgoedjen/secretive/actions/workflows/nightly.yml")!) {
|
||||
Button(.updaterDownloadLatestNightlyButton) {}
|
||||
.frame(maxWidth: .infinity)
|
||||
.primaryButton()
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
UpdateDetailView(update: update)
|
||||
}
|
||||
UpdateDetailView(update: update)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -113,16 +117,7 @@ extension ContentView {
|
||||
Button(.appMenuNewSecretButton, systemImage: "plus") {
|
||||
showingCreation = true
|
||||
}
|
||||
.menuButton()
|
||||
.sheet(isPresented: $showingCreation) {
|
||||
if let modifiable = storeList.modifiableStore {
|
||||
CreateSecretView(store: modifiable) { created in
|
||||
if let created {
|
||||
activeSecret = created
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.toolbarCircleButton()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,7 +144,7 @@ extension ContentView {
|
||||
}
|
||||
})
|
||||
.buttonStyle(
|
||||
ToolbarButtonStyle(
|
||||
ToolbarStatusButtonStyle(
|
||||
lightColor: agentStatusChecker.running ? .black.opacity(0.05) : .red.opacity(0.75),
|
||||
darkColor: agentStatusChecker.running ? .white.opacity(0.05) : .red.opacity(0.5),
|
||||
)
|
||||
@@ -171,18 +166,18 @@ extension ContentView {
|
||||
.font(.headline)
|
||||
.foregroundColor(.white)
|
||||
})
|
||||
.buttonStyle(ToolbarButtonStyle(color: .orange))
|
||||
.popover(isPresented: $showingAppPathNotice, attachmentAnchor: attachmentAnchor, arrowEdge: .bottom) {
|
||||
VStack {
|
||||
Image(systemName: "exclamationmark.triangle")
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: 64)
|
||||
Text(.appNotInApplicationsNoticeDetailDescription)
|
||||
.frame(maxWidth: 300)
|
||||
.buttonStyle(ToolbarStatusButtonStyle(color: .orange))
|
||||
.confirmationDialog(.appNotInApplicationsNoticeTitle, isPresented: $showingAppPathNotice) {
|
||||
Button(.appNotInApplicationsNoticeCancelButton, role: .cancel) {
|
||||
}
|
||||
.padding()
|
||||
Button(.appNotInApplicationsNoticeQuitButton) {
|
||||
NSWorkspace.shared.selectFile(Bundle.main.bundlePath, inFileViewerRootedAtPath: Bundle.main.bundlePath)
|
||||
NSApplication.shared.terminate(nil)
|
||||
}
|
||||
} message: {
|
||||
Text(.appNotInApplicationsNoticeDetailDescription)
|
||||
}
|
||||
.dialogIcon(Image(systemName: "folder.fill.badge.questionmark"))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ struct CopyableView: View {
|
||||
@State private var interactionState: InteractionState = .normal
|
||||
|
||||
var content: some View {
|
||||
VStack(alignment: .leading) {
|
||||
VStack(alignment: .leading, spacing: 15) {
|
||||
HStack {
|
||||
image
|
||||
.renderingMode(.template)
|
||||
@@ -28,20 +28,19 @@ struct CopyableView: View {
|
||||
}
|
||||
copyButton
|
||||
}
|
||||
.foregroundColor(secondaryTextColor)
|
||||
.transition(.opacity)
|
||||
.foregroundColor(secondaryTextColor)
|
||||
.transition(.opacity)
|
||||
}
|
||||
|
||||
}
|
||||
.padding(EdgeInsets(top: 20, leading: 20, bottom: 10, trailing: 20))
|
||||
Divider()
|
||||
.ignoresSafeArea()
|
||||
Text(text)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.foregroundColor(primaryTextColor)
|
||||
.padding(EdgeInsets(top: 10, leading: 20, bottom: 20, trailing: 20))
|
||||
.multilineTextAlignment(.leading)
|
||||
.font(.system(.body, design: .monospaced))
|
||||
}
|
||||
.safeAreaPadding(20)
|
||||
._background(interactionState: interactionState)
|
||||
.frame(minWidth: 150, maxWidth: .infinity)
|
||||
}
|
||||
@@ -53,12 +52,12 @@ struct CopyableView: View {
|
||||
interactionState = hovering ? .hovering : .normal
|
||||
}
|
||||
}
|
||||
.onDrag({
|
||||
NSItemProvider(item: NSData(data: text.data(using: .utf8)!), typeIdentifier: UTType.utf8PlainText.identifier)
|
||||
}, preview: {
|
||||
content
|
||||
.draggable(text) {
|
||||
content
|
||||
.lineLimit(3)
|
||||
.frame(maxWidth: 300)
|
||||
._background(interactionState: .dragging)
|
||||
})
|
||||
}
|
||||
.onTapGesture {
|
||||
copy()
|
||||
withAnimation {
|
||||
@@ -79,7 +78,7 @@ struct CopyableView: View {
|
||||
var copyButton: some View {
|
||||
switch interactionState {
|
||||
case .hovering:
|
||||
Button(.copyableClickToCopyButton, systemImage: "document.on.document") {
|
||||
Button(.copyableClickToCopyButton, systemImage: "doc.on.doc") {
|
||||
withAnimation {
|
||||
// Button will eat the click, so we set interaction state manually.
|
||||
interactionState = .clicking
|
||||
@@ -159,7 +158,8 @@ fileprivate struct BackgroundViewModifier: ViewModifier {
|
||||
// Very thin opacity lets user hover anywhere over the view, glassEffect doesn't allow.
|
||||
.background(.white.opacity(0.01), in: RoundedRectangle(cornerRadius: 15))
|
||||
.glassEffect(.regular.tint(backgroundColor(interactionState: interactionState)), in: RoundedRectangle(cornerRadius: 15))
|
||||
|
||||
.mask(RoundedRectangle(cornerRadius: 15))
|
||||
.shadow(color: .black.opacity(0.1), radius: 5)
|
||||
} else {
|
||||
content
|
||||
.background(backgroundColor(interactionState: interactionState))
|
||||
@@ -170,13 +170,25 @@ fileprivate struct BackgroundViewModifier: ViewModifier {
|
||||
|
||||
func backgroundColor(interactionState: InteractionState) -> Color {
|
||||
guard appearsActive else { return Color.clear }
|
||||
switch interactionState {
|
||||
case .normal:
|
||||
return colorScheme == .dark ? Color(white: 0.2) : Color(white: 0.885)
|
||||
case .hovering, .dragging:
|
||||
return colorScheme == .dark ? Color(white: 0.275) : Color(white: 0.82)
|
||||
case .clicking:
|
||||
return .accentColor
|
||||
if #available(macOS 26.0, *) {
|
||||
let base = colorScheme == .dark ? Color(white: 0.2) : Color(white: 1)
|
||||
switch interactionState {
|
||||
case .normal:
|
||||
return base
|
||||
case .hovering:
|
||||
return base.mix(with: .accentColor, by: colorScheme == .dark ? 0.2 : 0.1)
|
||||
case .clicking, .dragging:
|
||||
return base.mix(with: .accentColor, by: 0.8)
|
||||
}
|
||||
} else {
|
||||
switch interactionState {
|
||||
case .normal:
|
||||
return colorScheme == .dark ? Color(white: 0.2) : Color(white: 0.885)
|
||||
case .hovering:
|
||||
return colorScheme == .dark ? Color(white: 0.275) : Color(white: 0.82)
|
||||
case .clicking, .dragging:
|
||||
return .accentColor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,7 +196,10 @@ fileprivate struct BackgroundViewModifier: ViewModifier {
|
||||
}
|
||||
|
||||
#Preview {
|
||||
CopyableView(title: .secretDetailSha256FingerprintLabel, image: Image(systemName: "figure.wave"), text: "Hello world.")
|
||||
VStack {
|
||||
CopyableView(title: .secretDetailSha256FingerprintLabel, image: Image(systemName: "figure.wave"), text: "Hello world.")
|
||||
CopyableView(title: .secretDetailSha256FingerprintLabel, image: Image(systemName: "figure.wave"), text: "Hello world.")
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
|
||||
|
||||
@@ -3,61 +3,50 @@ import Brief
|
||||
|
||||
struct UpdateDetailView: View {
|
||||
|
||||
@Environment(\.updater) var updater: any UpdaterProtocol
|
||||
@Environment(\.updater) var updater
|
||||
@Environment(\.openURL) var openURL
|
||||
|
||||
let update: Release
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
Text(.updateVersionName(updateName: update.name)).font(.title)
|
||||
GroupBox(label: Text(.updateReleaseNotesTitle)) {
|
||||
ScrollView {
|
||||
attributedBody
|
||||
}
|
||||
}
|
||||
HStack {
|
||||
if !update.critical {
|
||||
Button(.updateIgnoreButton) {
|
||||
Task {
|
||||
await updater.ignore(release: update)
|
||||
VStack(spacing: 0) {
|
||||
HStack {
|
||||
if !update.critical {
|
||||
Button(.updateIgnoreButton) {
|
||||
Task {
|
||||
await updater.ignore(release: update)
|
||||
}
|
||||
}
|
||||
.buttonStyle(ToolbarButtonStyle())
|
||||
}
|
||||
Spacer()
|
||||
if updater.currentVersion.isTestBuild {
|
||||
Button(.updaterDownloadLatestNightlyButton) {
|
||||
openURL(URL(string: "https://github.com/maxgoedjen/secretive/actions/workflows/nightly.yml")!)
|
||||
}
|
||||
.buttonStyle(ToolbarButtonStyle(tint: .accentColor))
|
||||
}
|
||||
Button(.updateUpdateButton) {
|
||||
openURL(update.html_url)
|
||||
}
|
||||
.buttonStyle(ToolbarButtonStyle(tint: .accentColor))
|
||||
.keyboardShortcut(.defaultAction)
|
||||
}
|
||||
Button(.updateUpdateButton) {
|
||||
NSWorkspace.shared.open(update.html_url)
|
||||
.padding()
|
||||
Divider()
|
||||
Form {
|
||||
Section {
|
||||
Text(update.attributedBody)
|
||||
} header: {
|
||||
Text(.updateVersionName(updateName: update.name)) .headerProminence(.increased)
|
||||
}
|
||||
}
|
||||
.keyboardShortcut(.defaultAction)
|
||||
}
|
||||
|
||||
.formStyle(.grouped)
|
||||
}
|
||||
.padding()
|
||||
.frame(maxWidth: 500)
|
||||
}
|
||||
|
||||
var attributedBody: Text {
|
||||
var text = Text(verbatim: "")
|
||||
for line in update.body.split(whereSeparator: \.isNewline) {
|
||||
let attributed: Text
|
||||
let split = line.split(separator: " ")
|
||||
let unprefixed = split.dropFirst().joined(separator: " ")
|
||||
if let prefix = split.first {
|
||||
switch prefix {
|
||||
case "#":
|
||||
attributed = Text(unprefixed).font(.title) + Text(verbatim: "\n")
|
||||
case "##":
|
||||
attributed = Text(unprefixed).font(.title2) + Text(verbatim: "\n")
|
||||
case "###":
|
||||
attributed = Text(unprefixed).font(.title3) + Text(verbatim: "\n")
|
||||
default:
|
||||
attributed = Text(line) + Text(verbatim: "\n\n")
|
||||
}
|
||||
} else {
|
||||
attributed = Text(line) + Text(verbatim: "\n\n")
|
||||
}
|
||||
text = text + attributed
|
||||
}
|
||||
return text
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#Preview {
|
||||
UpdateDetailView(update: .init(name: "3.0.0", prerelease: false, html_url: URL(string: "https://example.com")!, body: "Hello"))
|
||||
}
|
||||
|
||||
@@ -11,7 +11,9 @@ final class SecretiveUpdater: NSObject, XPCProtocol {
|
||||
|
||||
func process(_: Data) async throws -> [Release] {
|
||||
let (data, _) = try await URLSession.shared.data(from: Constants.updateURL)
|
||||
return try JSONDecoder().decode([Release].self, from: data)
|
||||
return try JSONDecoder()
|
||||
.decode([GitHubRelease].self, from: data)
|
||||
.map(Release.init)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||