Compare commits

..

23 Commits

Author SHA1 Message Date
Max Goedjen
1f74bd814f Include tag name in release upload command (#716) 2025-09-14 23:28:56 +00:00
Max Goedjen
d9d93574f2 Fix repeat setup (#712)
* Fix repeat setup

* Ideal width
2025-09-14 23:15:32 +00:00
Max Goedjen
15e8ed1ec2 Fix issue where “mark as migrated” could fail (#715) 2025-09-14 23:11:54 +00:00
Max Goedjen
1df0c8e96b Switch to icon composer source. (#714) 2025-09-14 23:01:18 +00:00
Max Goedjen
8213a8b451 Ideal width (#713) 2025-09-14 22:41:05 +00:00
Max Goedjen
af77fd4a21 Fix release digest formatting (#711) 2025-09-14 22:17:53 +00:00
Max Goedjen
85d0cab0f5 Disable preview (#710) 2025-09-14 21:53:13 +00:00
Max Goedjen
e8cdcdfb7f Adding some size fixing (#709) 2025-09-14 21:48:22 +00:00
Max Goedjen
d7f8d5e56b Add descriptions for unavailable keys (#708)
* Describe unavailable key types

* Cleanup
2025-09-14 21:42:41 +00:00
Max Goedjen
3f247d628f About screen. (#707) 2025-09-14 21:39:20 +00:00
Vladimir
dae9cead4e Update Russian localization (#706) 2025-09-14 21:05:34 +00:00
Max Goedjen
fe9f8613fa Fix move app later text (#705) 2025-09-14 08:47:40 +00:00
Max Goedjen
5d5ae5bab4 Add app folder notice. (#704) 2025-09-14 08:43:00 +00:00
Max Goedjen
f76766a9d5 Updater UI (#703)
* Parse markdown oop

* Update UI.

* Tweaks.
2025-09-14 08:20:10 +00:00
Max Goedjen
b308b10716 UI tweaks. (#701) 2025-09-14 00:03:20 +00:00
Max Goedjen
0e1e6813a1 Readme updates (#700) 2025-09-13 22:50:05 +00:00
Max Goedjen
27bf7c29e4 Fix deployment version for xpc services (#699) 2025-09-13 11:56:37 -07:00
Max Goedjen
36b6c52979 Logging for xpc input parser (#698) 2025-09-13 16:45:51 +00:00
Max Goedjen
67ec4fee12 More UI tweaks and fixes (#697)
* Integrations to window

* Cleanup of presenting.

* Older name for copy

* For copyable view too
2025-09-13 08:16:23 +00:00
Max Goedjen
21fc834fd9 Fix incorrect deletion of tracked files in public key standin folder. (#696) 2025-09-13 01:52:17 +00:00
Max Goedjen
726d0580d0 Fix minor ui glitches on older macOS (#695)
* Fix padding on toolbar buttons

* Fix sizing on setup view.
2025-09-12 08:40:57 +00:00
Max Goedjen
4f608ebbc6 Clear out needs review status (#694) 2025-09-12 01:57:09 +00:00
Max Goedjen
6e7cf82618 Fix quotes (#693)
* Fix up strings (hopefully)

* Few more

* Fixed back sides
2025-09-12 01:46:20 +00:00
48 changed files with 771 additions and 491 deletions

View File

@@ -55,7 +55,7 @@ jobs:
export CLEAN_TAG=$(echo $TAG_NAME | sed -e 's/refs\/tags\/v//') 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_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_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 - name: Build
run: xcrun xcodebuild -project Sources/Secretive.xcodeproj -scheme Secretive -configuration Release -archivePath Archive.xcarchive archive run: xcrun xcodebuild -project Sources/Secretive.xcodeproj -scheme Secretive -configuration Release -archivePath Archive.xcarchive archive
- name: Create ZIP - name: Create ZIP
@@ -77,13 +77,13 @@ jobs:
uses: actions/attest-build-provenance@v2 uses: actions/attest-build-provenance@v2
with: with:
subject-name: "Secretive.zip" subject-name: "Secretive.zip"
subject-digest: ${{ steps.upload.outputs.artifact-digest }} subject-digest: sha256:${{ steps.upload.outputs.artifact-digest }}
- name: Create Release - name: Create Release
run: | run: |
sed -i.tmp "s/RUN_ID/$RUN_ID/g" .github/templates/release.md 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 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 create $TAG_NAME -d -F .github/templates/release.md
gh release upload Secretive.zip gh release upload $TAG_NAME Secretive.zip
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG_NAME: ${{ github.ref }} TAG_NAME: ${{ github.ref }}

View File

@@ -1,8 +1,7 @@
# Secretive [![Test](https://github.com/maxgoedjen/secretive/actions/workflows/test.yml/badge.svg?branch=main)](https://github.com/maxgoedjen/secretive/actions/workflows/test.yml) ![Release](https://github.com/maxgoedjen/secretive/workflows/Release/badge.svg) # Secretive [![Test](https://github.com/maxgoedjen/secretive/actions/workflows/test.yml/badge.svg?branch=main)](https://github.com/maxgoedjen/secretive/actions/workflows/test.yml) ![Release](https://github.com/maxgoedjen/secretive/workflows/Release/badge.svg)
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> <picture>
<source media="(prefers-color-scheme: dark)" srcset="/.github/readme/app-dark.png"> <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"> <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 ## 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) 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).

View File

@@ -1,2 +1,3 @@
CI_VERSION = GITHUB_CI_VERSION CI_VERSION = GITHUB_CI_VERSION
CI_BUILD_NUMBER = GITHUB_BUILD_NUMBER CI_BUILD_NUMBER = GITHUB_BUILD_NUMBER
CI_BUILD_LINK = GITHUB_BUILD_URL

View File

@@ -177,6 +177,73 @@
"value" : "" "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" : { "agent_details_could_not_start_error" : {
@@ -316,8 +383,8 @@
}, },
"ru" : { "ru" : {
"stringUnit" : { "stringUnit" : {
"state" : "new", "state" : "translated",
"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." "value" : "Secretive не получлось запустить SecretAgent. Пожалуйста, попробуйте перезапустить Ваш Mac, и если это не поможет создайте issue на GitHub."
} }
}, },
"sr" : { "sr" : {
@@ -501,8 +568,8 @@
}, },
"ru" : { "ru" : {
"stringUnit" : { "stringUnit" : {
"state" : "new", "state" : "translated",
"value" : "Disable Agent" "value" : "Отключить агент"
} }
}, },
"sr" : { "sr" : {
@@ -686,8 +753,8 @@
}, },
"ru" : { "ru" : {
"stringUnit" : { "stringUnit" : {
"state" : "new", "state" : "translated",
"value" : "Restart Agent" "value" : "Перезапустить агент"
} }
}, },
"sr" : { "sr" : {
@@ -871,8 +938,8 @@
}, },
"ru" : { "ru" : {
"stringUnit" : { "stringUnit" : {
"state" : "new", "state" : "translated",
"value" : "Running Since" "value" : "Запущено с"
} }
}, },
"sr" : { "sr" : {
@@ -1056,8 +1123,8 @@
}, },
"ru" : { "ru" : {
"stringUnit" : { "stringUnit" : {
"state" : "new", "state" : "translated",
"value" : "Socket Path" "value" : "Путь к сокету"
} }
}, },
"sr" : { "sr" : {
@@ -1241,8 +1308,8 @@
}, },
"ru" : { "ru" : {
"stringUnit" : { "stringUnit" : {
"state" : "new", "state" : "translated",
"value" : "Start Agent" "value" : "Запустить агент"
} }
}, },
"sr" : { "sr" : {
@@ -1426,8 +1493,8 @@
}, },
"ru" : { "ru" : {
"stringUnit" : { "stringUnit" : {
"state" : "new", "state" : "translated",
"value" : "Starting Agent" "value" : "Агент запускается"
} }
}, },
"sr" : { "sr" : {
@@ -1611,8 +1678,8 @@
}, },
"ru" : { "ru" : {
"stringUnit" : { "stringUnit" : {
"state" : "new", "state" : "translated",
"value" : "Version" "value" : "Версия"
} }
}, },
"sr" : { "sr" : {
@@ -1796,8 +1863,8 @@
}, },
"ru" : { "ru" : {
"stringUnit" : { "stringUnit" : {
"state" : "new", "state" : "translated",
"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.**" "value" : "SecretAgent это процесс, который работает в фоне чтобы подписывать запросы. Так Вам не придется все время держать Secretive открытым.\n\n**Secretive не сможет нормально функционировать, пока агент не установлен и не запущен.**"
} }
}, },
"sr" : { "sr" : {
@@ -2721,8 +2788,8 @@
}, },
"ru" : { "ru" : {
"stringUnit" : { "stringUnit" : {
"state" : "new", "state" : "translated",
"value" : "Secret Agent Location" "value" : "Расположение Secret Agent"
} }
}, },
"sr" : { "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" : { "app_not_in_applications_notice_detail_description" : {
"extractionState" : "manual", "extractionState" : "manual",
"localizations" : { "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" : { "app_not_in_applications_notice_title" : {
"extractionState" : "manual", "extractionState" : "manual",
"localizations" : { "localizations" : {
@@ -4574,8 +4675,8 @@
}, },
"ru" : { "ru" : {
"stringUnit" : { "stringUnit" : {
"state" : "new", "state" : "translated",
"value" : "Advanced" "value" : "Расширенное"
} }
}, },
"sr" : { "sr" : {
@@ -4759,8 +4860,8 @@
}, },
"ru" : { "ru" : {
"stringUnit" : { "stringUnit" : {
"state" : "new", "state" : "translated",
"value" : "If you change your biometric settings in _any way_, including adding a new fingerprint, this key will no longer be accessible." "value" : "Если Вы сделаете _любые_ изменения в Ваших настройках биометрии, включая добавление нового отпечатка пальца, этот ключ перестанет быть доступным."
} }
}, },
"sr" : { "sr" : {
@@ -5314,8 +5415,8 @@
}, },
"ru" : { "ru" : {
"stringUnit" : { "stringUnit" : {
"state" : "new", "state" : "translated",
"value" : "This shows at the end of your public key. Its usually an email address." "value" : "Это отображается в конце Вашего публичного ключа. Как правило, это email адрес."
} }
}, },
"sr" : { "sr" : {
@@ -5499,8 +5600,8 @@
}, },
"ru" : { "ru" : {
"stringUnit" : { "stringUnit" : {
"state" : "new", "state" : "translated",
"value" : "Key Attribution" "value" : "Принадлежность ключа"
} }
}, },
"sr" : { "sr" : {
@@ -5684,8 +5785,8 @@
}, },
"ru" : { "ru" : {
"stringUnit" : { "stringUnit" : {
"state" : "new", "state" : "translated",
"value" : "Key Type" "value" : "Тип ключа"
} }
}, },
"sr" : { "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" : { "create_secret_mldsa_warning" : {
"extractionState" : "manual", "extractionState" : "manual",
"localizations" : { "localizations" : {
@@ -5869,8 +5981,8 @@
}, },
"ru" : { "ru" : {
"stringUnit" : { "stringUnit" : {
"state" : "new", "state" : "translated",
"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." "value" : "Внимание: Ключи ML-DSA совсем новые, и не пока поддерживаются многими серверами. Пожалуйста удостоверьтесь, что используемый с этим ключом сервер поддерживает ключи ML-DSA."
} }
}, },
"sr" : { "sr" : {
@@ -6425,7 +6537,7 @@
"ru" : { "ru" : {
"stringUnit" : { "stringUnit" : {
"state" : "translated", "state" : "translated",
"value" : "Аутентификация не потребуется пока Ваш Mac разблокирован, но Вы получите уведомление, если секрет был использован." "value" : "Аутентификация не потребуется пока Ваш Mac разблокирован, но Вы получите уведомление, если секрет будет использован."
} }
}, },
"sr" : { "sr" : {
@@ -6794,8 +6906,8 @@
}, },
"ru" : { "ru" : {
"stringUnit" : { "stringUnit" : {
"state" : "new", "state" : "translated",
"value" : "Protection Level" "value" : "Уровень защиты"
} }
}, },
"sr" : { "sr" : {
@@ -6979,8 +7091,8 @@
}, },
"ru" : { "ru" : {
"stringUnit" : { "stringUnit" : {
"state" : "new", "state" : "translated",
"value" : "Require authentication with current set of biometrics." "value" : "Требовать аутентификацию с текущим набором биометрии."
} }
}, },
"sr" : { "sr" : {
@@ -7164,8 +7276,8 @@
}, },
"ru" : { "ru" : {
"stringUnit" : { "stringUnit" : {
"state" : "new", "state" : "translated",
"value" : "Current Biometrics" "value" : "Текущая биометрия"
} }
}, },
"sr" : { "sr" : {
@@ -9384,8 +9496,8 @@
}, },
"ru" : { "ru" : {
"stringUnit" : { "stringUnit" : {
"state" : "new", "state" : "translated",
"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." "value" : "Похоже, что Вы могли недавно обновить MacOS. Иногда из-за этого возникают проблемы с Secure Enclave, и возможно Вам потребуется перезапустить Ваш Mac, чтобы их исправить."
} }
}, },
"sr" : { "sr" : {
@@ -9569,8 +9681,8 @@
}, },
"ru" : { "ru" : {
"stringUnit" : { "stringUnit" : {
"state" : "new", "state" : "translated",
"value" : "Missing Secrets?" "value" : "Не хватает секретов?"
} }
}, },
"sr" : { "sr" : {
@@ -10667,8 +10779,8 @@
}, },
"ru" : { "ru" : {
"stringUnit" : { "stringUnit" : {
"state" : "new", "state" : "translated",
"value" : "Add This:" "value" : "Добавить следующее:"
} }
}, },
"sr" : { "sr" : {
@@ -10852,8 +10964,8 @@
}, },
"ru" : { "ru" : {
"stringUnit" : { "stringUnit" : {
"state" : "new", "state" : "translated",
"value" : "Apps" "value" : "Приложения"
} }
}, },
"sr" : { "sr" : {
@@ -11037,8 +11149,8 @@
}, },
"ru" : { "ru" : {
"stringUnit" : { "stringUnit" : {
"state" : "new", "state" : "translated",
"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." "value" : "Существует список инструкций, поддерживаемый сообществом, к приложениям на GitHub. Если нужное Вам приложение не поддерживается, создайте issue, возможно сообщество сможет помочь Вам."
} }
}, },
"sr" : { "sr" : {
@@ -11222,8 +11334,8 @@
}, },
"ru" : { "ru" : {
"stringUnit" : { "stringUnit" : {
"state" : "new", "state" : "translated",
"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." "value" : "Существует список shell-скриптов, поддерживаемый сообществом на GitHub. Если нужный Вам shell-скрипт не поддерживается, создайте issue, возможно сообщество сможет помочь Вам."
} }
}, },
"sr" : { "sr" : {
@@ -11407,8 +11519,8 @@
}, },
"ru" : { "ru" : {
"stringUnit" : { "stringUnit" : {
"state" : "new", "state" : "translated",
"value" : "You'll need to create a Secret before configuring this action." "value" : "Вам потребуется создать секрет перед настройкой этого действия."
} }
}, },
"sr" : { "sr" : {
@@ -11592,8 +11704,8 @@
}, },
"ru" : { "ru" : {
"stringUnit" : { "stringUnit" : {
"state" : "new", "state" : "translated",
"value" : "Configure Using Secret" "value" : "Настроить используя секрет"
} }
}, },
"sr" : { "sr" : {
@@ -11777,8 +11889,8 @@
}, },
"ru" : { "ru" : {
"stringUnit" : { "stringUnit" : {
"state" : "new", "state" : "translated",
"value" : "No Secret" "value" : "Без секрета"
} }
}, },
"sr" : { "sr" : {
@@ -11962,8 +12074,8 @@
}, },
"ru" : { "ru" : {
"stringUnit" : { "stringUnit" : {
"state" : "new", "state" : "translated",
"value" : "Secret" "value" : "С секретом"
} }
}, },
"sr" : { "sr" : {
@@ -12147,8 +12259,8 @@
}, },
"ru" : { "ru" : {
"stringUnit" : { "stringUnit" : {
"state" : "new", "state" : "translated",
"value" : "You can configure more than one tool, they generally won't interfere with each other." "value" : "Вы можете настроить более чем один инструмент. Обычно это не вызывает конфликтов при работе."
} }
}, },
"sr" : { "sr" : {
@@ -12332,8 +12444,8 @@
}, },
"ru" : { "ru" : {
"stringUnit" : { "stringUnit" : {
"state" : "new", "state" : "translated",
"value" : "Getting Started" "value" : "Начало работы"
} }
}, },
"sr" : { "sr" : {
@@ -12517,8 +12629,8 @@
}, },
"ru" : { "ru" : {
"stringUnit" : { "stringUnit" : {
"state" : "new", "state" : "translated",
"value" : "Integrations" "value" : "Интеграции"
} }
}, },
"sr" : { "sr" : {
@@ -12702,8 +12814,8 @@
}, },
"ru" : { "ru" : {
"stringUnit" : { "stringUnit" : {
"state" : "new", "state" : "translated",
"value" : "If you're trying to sign your git commits, set up Git Signing." "value" : "Если Вы хотите подписывать ваши git коммиты, настройте подпись Git."
} }
}, },
"sr" : { "sr" : {
@@ -12887,8 +12999,8 @@
}, },
"ru" : { "ru" : {
"stringUnit" : { "stringUnit" : {
"state" : "new", "state" : "translated",
"value" : "If you're trying to configure anything your command line runs to use Secretive, configure your shell." "value" : "Если Вы хотите наладить работу Secretive с Вашими скриптами в Терминале, настройте Ваш Терминал."
} }
}, },
"sr" : { "sr" : {
@@ -13072,8 +13184,8 @@
}, },
"ru" : { "ru" : {
"stringUnit" : { "stringUnit" : {
"state" : "new", "state" : "translated",
"value" : "If you don't known what shell you use and haven't changed it, you're probably using `%(shellName)@`." "value" : "Если Вы не знаете, какой Терминал используете и не меняли его раннее, то скорее всего Вы используете `%(shellName)@`."
} }
}, },
"sr" : { "sr" : {
@@ -13257,8 +13369,8 @@
}, },
"ru" : { "ru" : {
"stringUnit" : { "stringUnit" : {
"state" : "new", "state" : "translated",
"value" : "If you're trying to authenticate with an SSH server or authenticating with a service like GitHub over SSH, configure your SSH client." "value" : "Если Вы хотите аутентифицироваться с SSH сервером, или с сервисом как GitHub через SSH, настройте Ваш SSH клиент."
} }
}, },
"sr" : { "sr" : {
@@ -13442,8 +13554,8 @@
}, },
"ru" : { "ru" : {
"stringUnit" : { "stringUnit" : {
"state" : "new", "state" : "translated",
"value" : "Configuring Tools for Secretive" "value" : "Настройка инструментов для Secretive"
} }
}, },
"sr" : { "sr" : {
@@ -13627,8 +13739,8 @@
}, },
"ru" : { "ru" : {
"stringUnit" : { "stringUnit" : {
"state" : "new", "state" : "translated",
"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." "value" : "Большинство инструментов попробуют найти SSH ключи в `~/.ssh`. Для того, чтобы использовать Secretive, нам потребуется настроить эти инструменты, чтобы они работали с Secretive."
} }
}, },
"sr" : { "sr" : {
@@ -13812,8 +13924,8 @@
}, },
"ru" : { "ru" : {
"stringUnit" : { "stringUnit" : {
"state" : "new", "state" : "translated",
"value" : "What Should I Configure?" "value" : "Что мне следует настроить?"
} }
}, },
"sr" : { "sr" : {
@@ -13997,8 +14109,8 @@
}, },
"ru" : { "ru" : {
"stringUnit" : { "stringUnit" : {
"state" : "new", "state" : "translated",
"value" : "~/.gitallowedsigners probably does not exist. You'll need to create it." "value" : "Возможно, директория ~/.gitallowedsigners не существует. Вам потребуется создать ее."
} }
}, },
"sr" : { "sr" : {
@@ -14368,8 +14480,8 @@
}, },
"ru" : { "ru" : {
"stringUnit" : { "stringUnit" : {
"state" : "new", "state" : "translated",
"value" : "Integrations…" "value" : "Интеграции…"
} }
}, },
"sr" : { "sr" : {
@@ -14553,8 +14665,8 @@
}, },
"ru" : { "ru" : {
"stringUnit" : { "stringUnit" : {
"state" : "new", "state" : "translated",
"value" : "Other" "value" : "Другое"
} }
}, },
"sr" : { "sr" : {
@@ -14738,8 +14850,8 @@
}, },
"ru" : { "ru" : {
"stringUnit" : { "stringUnit" : {
"state" : "new", "state" : "translated",
"value" : "other" "value" : "другое"
} }
}, },
"sr" : { "sr" : {
@@ -14923,8 +15035,8 @@
}, },
"ru" : { "ru" : {
"stringUnit" : { "stringUnit" : {
"state" : "new", "state" : "translated",
"value" : "Configuration File" "value" : "Файл конфигурации"
} }
}, },
"sr" : { "sr" : {
@@ -15108,7 +15220,7 @@
}, },
"ru" : { "ru" : {
"stringUnit" : { "stringUnit" : {
"state" : "new", "state" : "translated",
"value" : "Shell" "value" : "Shell"
} }
}, },
@@ -15293,8 +15405,8 @@
}, },
"ru" : { "ru" : {
"stringUnit" : { "stringUnit" : {
"state" : "new", "state" : "translated",
"value" : "You can tell SSH to use a specific key for a given host. See the web documentation for more details." "value" : "Вы можете настроить SSH так, чтобы использовался конкретный ключ для выбранного хоста. Посмотрите документацию в интернете, чтобы узнать подробности."
} }
}, },
"sr" : { "sr" : {
@@ -15478,8 +15590,8 @@
}, },
"ru" : { "ru" : {
"stringUnit" : { "stringUnit" : {
"state" : "new", "state" : "translated",
"value" : "System" "value" : "Система"
} }
}, },
"sr" : { "sr" : {
@@ -16035,8 +16147,8 @@
}, },
"ru" : { "ru" : {
"stringUnit" : { "stringUnit" : {
"state" : "new", "state" : "translated",
"value" : "Git Signing" "value" : "Подпись Git"
} }
}, },
"sr" : { "sr" : {
@@ -16592,8 +16704,8 @@
}, },
"ru" : { "ru" : {
"stringUnit" : { "stringUnit" : {
"state" : "new", "state" : "translated",
"value" : "View on GitHub" "value" : "Посмотреть на GitHub"
} }
}, },
"sr" : { "sr" : {
@@ -16777,8 +16889,8 @@
}, },
"ru" : { "ru" : {
"stringUnit" : { "stringUnit" : {
"state" : "new", "state" : "translated",
"value" : "View Documentation on Web" "value" : "Найти документацию в интернете"
} }
}, },
"sr" : { "sr" : {
@@ -16962,8 +17074,8 @@
}, },
"ru" : { "ru" : {
"stringUnit" : { "stringUnit" : {
"state" : "new", "state" : "translated",
"value" : "If any section (like [user]) already exists, just add the entries in the existing section." "value" : "Если любой раздел (как [user]) уже существует, просто добавьте новые записи в него."
} }
}, },
"sr" : { "sr" : {
@@ -17890,7 +18002,7 @@
"ru" : { "ru" : {
"stringUnit" : { "stringUnit" : {
"state" : "translated", "state" : "translated",
"value" : "Не разблокировывать" "value" : "Не разблокировать"
} }
}, },
"sr" : { "sr" : {
@@ -18074,8 +18186,8 @@
}, },
"ru" : { "ru" : {
"stringUnit" : { "stringUnit" : {
"state" : "new", "state" : "translated",
"value" : "Reveal in Finder" "value" : "Открыть в Finder"
} }
}, },
"sr" : { "sr" : {
@@ -19369,7 +19481,7 @@
}, },
"ru" : { "ru" : {
"stringUnit" : { "stringUnit" : {
"state" : "new", "state" : "translated",
"value" : "Secure Enclave" "value" : "Secure Enclave"
} }
}, },
@@ -19734,7 +19846,7 @@
"ru" : { "ru" : {
"stringUnit" : { "stringUnit" : {
"state" : "translated", "state" : "translated",
"value" : "Это вспомогательное приложение назвается **Secret Agent**, Вы можете наблюдать его в Activity Monitor время от времени." "value" : "Это вспомогательное приложение назвается **Secret Agent**, Вы можете видеть его в Activity Monitor время от времени."
} }
}, },
"sr" : { "sr" : {
@@ -20289,7 +20401,7 @@
"ru" : { "ru" : {
"stringUnit" : { "stringUnit" : {
"state" : "translated", "state" : "translated",
"value" : "Настроить агента" "value" : "Настроить агент"
} }
}, },
"sr" : { "sr" : {
@@ -20473,8 +20585,8 @@
}, },
"ru" : { "ru" : {
"stringUnit" : { "stringUnit" : {
"state" : "new", "state" : "translated",
"value" : "Done" "value" : "Готово"
} }
}, },
"sr" : { "sr" : {
@@ -20658,8 +20770,8 @@
}, },
"ru" : { "ru" : {
"stringUnit" : { "stringUnit" : {
"state" : "new", "state" : "translated",
"value" : "Configure" "value" : "Настроить"
} }
}, },
"sr" : { "sr" : {
@@ -20843,8 +20955,8 @@
}, },
"ru" : { "ru" : {
"stringUnit" : { "stringUnit" : {
"state" : "new", "state" : "translated",
"value" : "Tell the tools you use how to talk to Secretive." "value" : "Настройте Ваши инструменты для работы с Secretive"
} }
}, },
"sr" : { "sr" : {
@@ -21028,8 +21140,8 @@
}, },
"ru" : { "ru" : {
"stringUnit" : { "stringUnit" : {
"state" : "new", "state" : "translated",
"value" : "Configure Integrations" "value" : "Настроить интеграции"
} }
}, },
"sr" : { "sr" : {
@@ -21768,8 +21880,8 @@
}, },
"ru" : { "ru" : {
"stringUnit" : { "stringUnit" : {
"state" : "new", "state" : "translated",
"value" : "Done" "value" : "Готово"
} }
}, },
"sr" : { "sr" : {
@@ -24735,7 +24847,7 @@
}, },
"ru" : { "ru" : {
"stringUnit" : { "stringUnit" : {
"state" : "new", "state" : "translated",
"value" : "Secretive %1$(updateName)@" "value" : "Secretive %1$(updateName)@"
} }
}, },
@@ -24920,8 +25032,8 @@
}, },
"ru" : { "ru" : {
"stringUnit" : { "stringUnit" : {
"state" : "new", "state" : "translated",
"value" : "Download Latest Nightly Build" "value" : "Скачать последнюю ночную сборку"
} }
}, },
"sr" : { "sr" : {

View File

@@ -1,7 +1,8 @@
import Foundation import Foundation
import SwiftUI
/// A release is a representation of a downloadable update. /// 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" /// The user-facing name of the release. Typically "Secretive 1.2.3"
public let name: String public let name: String
@@ -15,6 +16,8 @@ public struct Release: Codable, Sendable {
/// A user-facing description of the contents of the update. /// A user-facing description of the contents of the update.
public let body: String public let body: String
public let attributedBody: AttributedString
/// Initializes a Release. /// Initializes a Release.
/// - Parameters: /// - Parameters:
/// - name: The user-facing name of the release. /// - name: The user-facing name of the release.
@@ -26,6 +29,56 @@ public struct Release: Codable, Sendable {
self.prerelease = prerelease self.prerelease = prerelease
self.html_url = html_url self.html_url = html_url
self.body = body 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"))
}
} }
} }

View File

@@ -64,7 +64,7 @@ public final class AnySecretStoreModifiable: AnySecretStore, SecretStoreModifiab
private let _create: @Sendable (String, Attributes) async throws -> AnySecret private let _create: @Sendable (String, Attributes) async throws -> AnySecret
private let _delete: @Sendable (AnySecret) async throws -> Void private let _delete: @Sendable (AnySecret) async throws -> Void
private let _update: @Sendable (AnySecret, String, Attributes) 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 { public init<SecretStoreType>(_ secretStore: SecretStoreType) where SecretStoreType: SecretStoreModifiable {
_create = { AnySecret(try await secretStore.create(name: $0, attributes: $1)) } _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) try await _update(secret, name, attributes)
} }
public var supportedKeyTypes: [KeyType] { public var supportedKeyTypes: KeyAvailability {
_supportedKeyTypes() _supportedKeyTypes()
} }

View File

@@ -19,9 +19,10 @@ public final class PublicKeyFileStoreController: Sendable {
public func generatePublicKeys(for secrets: [AnySecret], clear: Bool = false) throws { public func generatePublicKeys(for secrets: [AnySecret], clear: Bool = false) throws {
logger.log("Writing public keys to disk") logger.log("Writing public keys to disk")
if clear { 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 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) let untracked = Set(fullPathContents)
.subtracting(validPaths) .subtracting(validPaths)

View File

@@ -62,10 +62,37 @@ public protocol SecretStoreModifiable<SecretType>: SecretStore {
/// - attributes: The new attributes for the secret. /// - attributes: The new attributes for the secret.
func update(secret: SecretType, name: String, attributes: Attributes) async throws 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 { extension NSNotification.Name {
// Distributed notification that keys were modified out of process (ie, that the management tool added/removed secrets) // Distributed notification that keys were modified out of process (ie, that the management tool added/removed secrets)

View File

@@ -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)) 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 { guard !migratedPublicKeys.contains(parsed.publicKey.x963Representation) else {
logger.log("Skipping \(name), public key already present. Marking as migrated.") logger.log("Skipping \(name), public key already present. Marking as migrated.")
try markMigrated(secret: secret, oldID: id) markMigrated(secret: secret, oldID: id)
continue continue
} }
logger.log("Migrating \(name).") logger.log("Migrating \(name).")
try store.saveKey(tokenObjectID, name: name, attributes: secret.attributes) try store.saveKey(tokenObjectID, name: name, attributes: secret.attributes)
logger.log("Migrated \(name).") logger.log("Migrated \(name).")
try markMigrated(secret: secret, oldID: id) markMigrated(secret: secret, oldID: id)
migratedAny = true migratedAny = true
} catch { } catch {
logger.error("Failed to migrate \(name): \(error).") logger.error("Failed to migrate \(name): \(error.localizedDescription).")
} }
} }
if migratedAny { 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([ let updateQuery = KeychainDictionary([
kSecClass: kSecClassKey, kSecClass: kSecClassKey,
kSecAttrApplicationLabel: secret.id kSecAttrApplicationLabel: oldID
]) ])
let newID = oldID + Constants.migrationMagicNumber let newID = oldID + Constants.migrationMagicNumber
@@ -82,7 +82,7 @@ extension SecureEnclave {
let status = SecItemUpdate(updateQuery, updatedAttributes) let status = SecItemUpdate(updateQuery, updatedAttributes)
if status != errSecSuccess { if status != errSecSuccess {
throw KeychainError(statusCode: status) logger.warning("Failed to mark \(secret.name) as migrated: \(status).")
} }
} }

View File

@@ -186,17 +186,22 @@ extension SecureEnclave {
await reloadSecrets() await reloadSecrets()
} }
public var supportedKeyTypes: [KeyType] { public let supportedKeyTypes: KeyAvailability = {
if #available(macOS 26, *) { let macOS26Keys: [KeyType] = [.mldsa65, .mldsa87]
[ let isAtLeastMacOS26 = if #available(macOS 26, *) {
.ecdsa256, true
.mldsa65,
.mldsa87,
]
} else { } else {
[.ecdsa256] false
} }
return KeyAvailability(
available: [
.ecdsa256,
] + (isAtLeastMacOS26 ? macOS26Keys : []),
unavailable: (isAtLeastMacOS26 ? [] : macOS26Keys).map {
KeyAvailability.UnavailableKeyType(keyType: $0, reason: .macOSUpdateRequired)
} }
)
}()
} }
} }

View File

@@ -34,7 +34,9 @@ public final class XPCServiceDelegate: NSObject, NSXPCListenerDelegate {
if let error = error as? Codable & Error { if let error = error as? Codable & Error {
reply(nil, NSError(error)) reply(nil, NSError(error))
} else { } 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]))
} }
} }
} }

View File

@@ -2,18 +2,24 @@ import Foundation
import SecretAgentKit import SecretAgentKit
import Brief import Brief
import XPCWrappers import XPCWrappers
import OSLog
/// Delegates all agent input parsing to an XPC service which wraps OpenSSH /// Delegates all agent input parsing to an XPC service which wraps OpenSSH
public final class XPCAgentInputParser: SSHAgentInputParserProtocol { public final class XPCAgentInputParser: SSHAgentInputParserProtocol {
private let logger = Logger(subsystem: "com.maxgoedjen.secretive.secretagent", category: "XPCAgentInputParser")
private let session: XPCTypedSession<SSHAgent.Request, SSHAgentInputParser.AgentParsingError> private let session: XPCTypedSession<SSHAgent.Request, SSHAgentInputParser.AgentParsingError>
public init() async throws { public init() async throws {
logger.debug("Creating XPCAgentInputParser")
session = try await XPCTypedSession(serviceName: "com.maxgoedjen.Secretive.SecretAgentInputParser", warmup: true) 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 { 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 { deinit {

View File

@@ -19,7 +19,6 @@
5003EF632780081B00DF2006 /* SecureEnclaveSecretKit in Frameworks */ = {isa = PBXBuildFile; productRef = 5003EF622780081B00DF2006 /* SecureEnclaveSecretKit */; }; 5003EF632780081B00DF2006 /* SecureEnclaveSecretKit in Frameworks */ = {isa = PBXBuildFile; productRef = 5003EF622780081B00DF2006 /* SecureEnclaveSecretKit */; };
5003EF652780081B00DF2006 /* SmartCardSecretKit in Frameworks */ = {isa = PBXBuildFile; productRef = 5003EF642780081B00DF2006 /* SmartCardSecretKit */; }; 5003EF652780081B00DF2006 /* SmartCardSecretKit in Frameworks */ = {isa = PBXBuildFile; productRef = 5003EF642780081B00DF2006 /* SmartCardSecretKit */; };
5008C23E2E525D8900507AC2 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 5008C23D2E525D8200507AC2 /* Localizable.xcstrings */; }; 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 */; }; 5008C2412E52D18700507AC2 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 5008C23D2E525D8200507AC2 /* Localizable.xcstrings */; };
501421622781262300BBAA70 /* Brief in Frameworks */ = {isa = PBXBuildFile; productRef = 501421612781262300BBAA70 /* Brief */; }; 501421622781262300BBAA70 /* Brief in Frameworks */ = {isa = PBXBuildFile; productRef = 501421612781262300BBAA70 /* Brief */; };
501421652781268000BBAA70 /* SecretAgent.app in CopyFiles */ = {isa = PBXBuildFile; fileRef = 50A3B78A24026B7500D209EA /* SecretAgent.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 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 */; }; 50571E0524393D1500F76F6C /* LaunchAgentController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50571E0424393D1500F76F6C /* LaunchAgentController.swift */; };
50617D8323FCE48E0099B055 /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50617D8223FCE48E0099B055 /* App.swift */; }; 50617D8323FCE48E0099B055 /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50617D8223FCE48E0099B055 /* App.swift */; };
50617D8523FCE48E0099B055 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50617D8423FCE48E0099B055 /* ContentView.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 */; }; 50617D8A23FCE48E0099B055 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 50617D8923FCE48E0099B055 /* Preview Assets.xcassets */; };
50617DD223FCEFA90099B055 /* PreviewStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50617DD123FCEFA90099B055 /* PreviewStore.swift */; }; 50617DD223FCEFA90099B055 /* PreviewStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50617DD123FCEFA90099B055 /* PreviewStore.swift */; };
5065E313295517C500E16645 /* ToolbarButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5065E312295517C500E16645 /* ToolbarButtonStyle.swift */; }; 5065E313295517C500E16645 /* ToolbarButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5065E312295517C500E16645 /* ToolbarButtonStyle.swift */; };
5066A6C22516F303004B5A36 /* SetupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5066A6C12516F303004B5A36 /* SetupView.swift */; }; 5066A6C22516F303004B5A36 /* SetupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5066A6C12516F303004B5A36 /* SetupView.swift */; };
5066A6C82516FE6E004B5A36 /* CopyableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5066A6C72516FE6E004B5A36 /* CopyableView.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 */; }; 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, ); }; }; 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 */; }; 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 */; }; 50BDCB762E6450950072D2E7 /* ConfigurationItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50BDCB752E6450950072D2E7 /* ConfigurationItemView.swift */; };
50C385A52407A76D00AF2719 /* SecretDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50C385A42407A76D00AF2719 /* SecretDetailView.swift */; }; 50C385A52407A76D00AF2719 /* SecretDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50C385A42407A76D00AF2719 /* SecretDetailView.swift */; };
50CF4ABC2E601B0F005588DC /* ActionButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50CF4ABB2E601B0F005588DC /* ActionButtonStyle.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 */ /* End PBXBuildFile section */
/* Begin PBXContainerItemProxy 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; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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; }; 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>"; }; 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>"; }; 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>"; }; 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 */ /* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */ /* Begin PBXFrameworksBuildPhase section */
@@ -293,15 +295,16 @@
path = Helpers; path = Helpers;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
504788ED2E681EB200B4556F /* Styles */ = { 504788ED2E681EB200B4556F /* Modifiers */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
50E4C4522E73C78900C73783 /* WindowBackgroundStyle.swift */,
50CF4ABB2E601B0F005588DC /* ActionButtonStyle.swift */, 50CF4ABB2E601B0F005588DC /* ActionButtonStyle.swift */,
50BDCB732E6436C60072D2E7 /* ErrorStyle.swift */, 50BDCB732E6436C60072D2E7 /* ErrorStyle.swift */,
504789222E697DD300B4556F /* BoxBackgroundStyle.swift */, 504789222E697DD300B4556F /* BoxBackgroundStyle.swift */,
5065E312295517C500E16645 /* ToolbarButtonStyle.swift */, 5065E312295517C500E16645 /* ToolbarButtonStyle.swift */,
); );
path = Styles; path = Modifiers;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
504788EE2E681EC300B4556F /* Secrets */ = { 504788EE2E681EC300B4556F /* Secrets */ = {
@@ -335,6 +338,7 @@
504788F02E681F0100B4556F /* Views */ = { 504788F02E681F0100B4556F /* Views */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
50E4C4C22E7765DF00C73783 /* AboutView.swift */,
50BDCB712E63BAF20072D2E7 /* AgentStatusView.swift */, 50BDCB712E63BAF20072D2E7 /* AgentStatusView.swift */,
50617D8423FCE48E0099B055 /* ContentView.swift */, 50617D8423FCE48E0099B055 /* ContentView.swift */,
5066A6C72516FE6E004B5A36 /* CopyableView.swift */, 5066A6C72516FE6E004B5A36 /* CopyableView.swift */,
@@ -375,11 +379,10 @@
508A58B0241ED1C40069DC07 /* Views */, 508A58B0241ED1C40069DC07 /* Views */,
508A58B1241ED1EA0069DC07 /* Controllers */, 508A58B1241ED1EA0069DC07 /* Controllers */,
50033AC427813F1C00253856 /* Helpers */, 50033AC427813F1C00253856 /* Helpers */,
50617D8623FCE48E0099B055 /* Assets.xcassets */,
50617D8E23FCE48E0099B055 /* Info.plist */, 50617D8E23FCE48E0099B055 /* Info.plist */,
508BF28D25B4F005009EFB7E /* InternetAccessPolicy.plist */, 508BF28D25B4F005009EFB7E /* InternetAccessPolicy.plist */,
50E4C4C72E777E4200C73783 /* AppIcon.icon */,
50617D8F23FCE48E0099B055 /* Secretive.entitlements */, 50617D8F23FCE48E0099B055 /* Secretive.entitlements */,
506772C62424784600034DED /* Credits.rtf */,
5008C23D2E525D8200507AC2 /* Localizable.xcstrings */, 5008C23D2E525D8200507AC2 /* Localizable.xcstrings */,
50617D8823FCE48E0099B055 /* Preview Content */, 50617D8823FCE48E0099B055 /* Preview Content */,
); );
@@ -432,7 +435,7 @@
children = ( children = (
504788EF2E681ED700B4556F /* Configuration */, 504788EF2E681ED700B4556F /* Configuration */,
504788EE2E681EC300B4556F /* Secrets */, 504788EE2E681EC300B4556F /* Secrets */,
504788ED2E681EB200B4556F /* Styles */, 504788ED2E681EB200B4556F /* Modifiers */,
504788F02E681F0100B4556F /* Views */, 504788F02E681F0100B4556F /* Views */,
); );
path = Views; path = Views;
@@ -643,9 +646,8 @@
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
50617D8A23FCE48E0099B055 /* Preview Assets.xcassets in Resources */, 50617D8A23FCE48E0099B055 /* Preview Assets.xcassets in Resources */,
50E4C4C82E777E4200C73783 /* AppIcon.icon in Resources */,
5008C23E2E525D8900507AC2 /* Localizable.xcstrings in Resources */, 5008C23E2E525D8900507AC2 /* Localizable.xcstrings in Resources */,
50617D8723FCE48E0099B055 /* Assets.xcassets in Resources */,
506772C72424784600034DED /* Credits.rtf in Resources */,
508BF28E25B4F005009EFB7E /* InternetAccessPolicy.plist in Resources */, 508BF28E25B4F005009EFB7E /* InternetAccessPolicy.plist in Resources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
@@ -671,8 +673,8 @@
50A3B79724026B7600D209EA /* Main.storyboard in Resources */, 50A3B79724026B7600D209EA /* Main.storyboard in Resources */,
5008C2412E52D18700507AC2 /* Localizable.xcstrings in Resources */, 5008C2412E52D18700507AC2 /* Localizable.xcstrings in Resources */,
50A3B79424026B7600D209EA /* Preview Assets.xcassets in Resources */, 50A3B79424026B7600D209EA /* Preview Assets.xcassets in Resources */,
50E4C4C92E777E4200C73783 /* AppIcon.icon in Resources */,
508BF2AA25B4F1CB009EFB7E /* InternetAccessPolicy.plist in Resources */, 508BF2AA25B4F1CB009EFB7E /* InternetAccessPolicy.plist in Resources */,
5008C2402E52792400507AC2 /* Assets.xcassets in Resources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@@ -685,7 +687,9 @@
files = ( files = (
504788F22E681F3A00B4556F /* Instructions.swift in Sources */, 504788F22E681F3A00B4556F /* Instructions.swift in Sources */,
50BDCB742E6436CA0072D2E7 /* ErrorStyle.swift in Sources */, 50BDCB742E6436CA0072D2E7 /* ErrorStyle.swift in Sources */,
50E4C4C32E7765DF00C73783 /* AboutView.swift in Sources */,
2C4A9D2F2636FFD3008CC8E2 /* EditSecretView.swift in Sources */, 2C4A9D2F2636FFD3008CC8E2 /* EditSecretView.swift in Sources */,
50E4C4532E73C78C00C73783 /* WindowBackgroundStyle.swift in Sources */,
5091D2BC25183B830049FD9B /* ApplicationDirectoryController.swift in Sources */, 5091D2BC25183B830049FD9B /* ApplicationDirectoryController.swift in Sources */,
504788EC2E680DC800B4556F /* URLs.swift in Sources */, 504788EC2E680DC800B4556F /* URLs.swift in Sources */,
504789232E697DD300B4556F /* BoxBackgroundStyle.swift in Sources */, 504789232E697DD300B4556F /* BoxBackgroundStyle.swift in Sources */,
@@ -1045,7 +1049,7 @@
INFOPLIST_KEY_CFBundleDisplayName = SecretiveUpdater; INFOPLIST_KEY_CFBundleDisplayName = SecretiveUpdater;
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Max Goedjen. All rights reserved."; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Max Goedjen. All rights reserved.";
LOCALIZATION_PREFERS_STRING_CATALOGS = YES; LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MACOSX_DEPLOYMENT_TARGET = 26.0; MACOSX_DEPLOYMENT_TARGET = 14.0;
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretiveUpdater; PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretiveUpdater;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
@@ -1085,7 +1089,7 @@
INFOPLIST_KEY_CFBundleDisplayName = SecretiveUpdater; INFOPLIST_KEY_CFBundleDisplayName = SecretiveUpdater;
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Max Goedjen. All rights reserved."; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Max Goedjen. All rights reserved.";
LOCALIZATION_PREFERS_STRING_CATALOGS = YES; LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MACOSX_DEPLOYMENT_TARGET = 26.0; MACOSX_DEPLOYMENT_TARGET = 14.0;
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretiveUpdater; PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretiveUpdater;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
@@ -1127,7 +1131,7 @@
INFOPLIST_KEY_CFBundleDisplayName = SecretiveUpdater; INFOPLIST_KEY_CFBundleDisplayName = SecretiveUpdater;
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Max Goedjen. All rights reserved."; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Max Goedjen. All rights reserved.";
LOCALIZATION_PREFERS_STRING_CATALOGS = YES; LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MACOSX_DEPLOYMENT_TARGET = 26.0; MACOSX_DEPLOYMENT_TARGET = 14.0;
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretiveUpdater; PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretiveUpdater;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
@@ -1159,7 +1163,7 @@
INFOPLIST_KEY_CFBundleDisplayName = SecretAgentInputParser; INFOPLIST_KEY_CFBundleDisplayName = SecretAgentInputParser;
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Max Goedjen. All rights reserved."; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Max Goedjen. All rights reserved.";
LOCALIZATION_PREFERS_STRING_CATALOGS = YES; LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MACOSX_DEPLOYMENT_TARGET = 26.0; MACOSX_DEPLOYMENT_TARGET = 14.0;
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretAgentInputParser; PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretAgentInputParser;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
@@ -1189,7 +1193,7 @@
INFOPLIST_KEY_CFBundleDisplayName = SecretAgentInputParser; INFOPLIST_KEY_CFBundleDisplayName = SecretAgentInputParser;
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Max Goedjen. All rights reserved."; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Max Goedjen. All rights reserved.";
LOCALIZATION_PREFERS_STRING_CATALOGS = YES; LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MACOSX_DEPLOYMENT_TARGET = 26.0; MACOSX_DEPLOYMENT_TARGET = 14.0;
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretAgentInputParser; PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretAgentInputParser;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
@@ -1221,7 +1225,7 @@
INFOPLIST_KEY_CFBundleDisplayName = SecretAgentInputParser; INFOPLIST_KEY_CFBundleDisplayName = SecretAgentInputParser;
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Max Goedjen. All rights reserved."; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Max Goedjen. All rights reserved.";
LOCALIZATION_PREFERS_STRING_CATALOGS = YES; LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MACOSX_DEPLOYMENT_TARGET = 26.0; MACOSX_DEPLOYMENT_TARGET = 14.0;
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretAgentInputParser; PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretAgentInputParser;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";

View File

@@ -4,55 +4,18 @@ import SecureEnclaveSecretKit
import SmartCardSecretKit import SmartCardSecretKit
import Brief 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 @main
struct Secretive: App { struct Secretive: App {
@Environment(\.agentStatusChecker) var agentStatusChecker @Environment(\.agentStatusChecker) var agentStatusChecker
@Environment(\.justUpdatedChecker) var justUpdatedChecker @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 { @SceneBuilder var body: some Scene {
WindowGroup { WindowGroup {
ContentView(showingCreation: $showingCreation, runningSetup: $showingSetup, hasRunSetup: $hasRunSetup) ContentView()
.environment(EnvironmentValues._secretStoreList) .environment(EnvironmentValues._secretStoreList)
.onAppear {
if !hasRunSetup {
showingSetup = true
}
}
.onReceive(NotificationCenter.default.publisher(for: NSApplication.didBecomeActiveNotification)) { _ in .onReceive(NotificationCenter.default.publisher(for: NSApplication.didBecomeActiveNotification)) { _ in
@AppStorage("defaultsHasRunSetup") var hasRunSetup = false
guard hasRunSetup else { return } guard hasRunSetup else { return }
agentStatusChecker.check() agentStatusChecker.check()
if agentStatusChecker.running && justUpdatedChecker.justUpdatedBuild { if agentStatusChecker.running && justUpdatedChecker.justUpdatedBuild {
@@ -62,25 +25,52 @@ struct Secretive: App {
forceLaunchAgent() forceLaunchAgent()
} }
} }
.sheet(isPresented: $showingIntegrations) {
IntegrationsView()
}
} }
.commands { .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) { CommandGroup(before: CommandGroupPlacement.appSettings) {
Button(.integrationsMenuBarTitle, systemImage: "app.connected.to.app.below.fill") { Button(.integrationsMenuBarTitle, systemImage: "app.connected.to.app.below.fill") {
showingIntegrations = true openWindow(id: String(describing: IntegrationsView.self))
} }
} }
CommandGroup(after: CommandGroupPlacement.newItem) { CommandGroup(after: CommandGroupPlacement.newItem) {
Button(.appMenuNewSecretButton) { Button(.appMenuNewSecretButton, systemImage: "plus") {
showingCreation = true showCreateSecret?()
} }
.keyboardShortcut(KeyboardShortcut(KeyEquivalent("N"), modifiers: [.command, .shift])) .keyboardShortcut(KeyboardShortcut(KeyEquivalent("N"), modifiers: [.command, .shift]))
.disabled(showCreateSecret?.isEnabled == false)
} }
CommandGroup(replacing: .help) { CommandGroup(replacing: .help) {
Button(.appMenuHelpButton) { Button(.appMenuHelpButton) {
NSWorkspace.shared.open(Constants.helpURL) openURL(Constants.helpURL)
} }
} }
SidebarCommands() SidebarCommands()
@@ -113,8 +103,56 @@ extension Secretive {
} }
private enum Constants { private enum Constants {
static let helpURL = URL(string: "https://github.com/maxgoedjen/secretive/blob/main/FAQ.md")! 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()
}
}

View File

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 43 KiB

View File

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

View File

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 69 KiB

View File

@@ -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
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 856 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 356 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 356 KiB

View File

@@ -1,6 +0,0 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -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}}}

View File

@@ -24,6 +24,8 @@
<string>$(MACOSX_DEPLOYMENT_TARGET)</string> <string>$(MACOSX_DEPLOYMENT_TARGET)</string>
<key>NSHumanReadableCopyright</key> <key>NSHumanReadableCopyright</key>
<string>$(PRODUCT_NAME) is MIT Licensed.</string> <string>$(PRODUCT_NAME) is MIT Licensed.</string>
<key>GitHubBuildLog</key>
<string>$(CI_BUILD_LINK)</string>
<key>NSPrincipalClass</key> <key>NSPrincipalClass</key>
<string>NSApplication</string> <string>NSApplication</string>
<key>NSSupportsAutomaticTermination</key> <key>NSSupportsAutomaticTermination</key>

View File

@@ -60,16 +60,17 @@ extension Preview {
let id = UUID() let id = UUID()
var name: String { "Modifiable Preview Store" } var name: String { "Modifiable Preview Store" }
let secrets: [Secret] let secrets: [Secret]
var supportedKeyTypes: [KeyType] { var supportedKeyTypes: KeyAvailability {
if #available(macOS 26, *) { return KeyAvailability(
[ available: [
.ecdsa256, .ecdsa256,
.mldsa65, .mldsa65,
.mldsa87, .mldsa87
],
unavailable: [
.init(keyType: .ecdsa384, reason: .macOSUpdateRequired)
] ]
} else { )
[.ecdsa256]
}
} }
init(secrets: [Secret]) { init(secrets: [Secret]) {

View File

@@ -32,7 +32,7 @@ struct ConfigurationItemView<Content: View>: View {
Spacer() Spacer()
switch action { switch action {
case .copy(let string): case .copy(let string):
Button(.copyableClickToCopyButton, systemImage: "document.on.document") { Button(.copyableClickToCopyButton, systemImage: "doc.on.doc") {
NSPasteboard.general.declareTypes([.string], owner: nil) NSPasteboard.general.declareTypes([.string], owner: nil)
NSPasteboard.general.setString(string, forType: .string) NSPasteboard.general.setString(string, forType: .string)
} }

View File

@@ -22,46 +22,18 @@ struct IntegrationsView: View {
} }
} detail: { } detail: {
IntegrationsDetailView(selectedInstruction: $selectedInstruction) IntegrationsDetailView(selectedInstruction: $selectedInstruction)
.fauxToolbar { }
.toolbar {
Button(.setupDoneButton) { Button(.setupDoneButton) {
dismiss() dismiss()
} }
.normalButton()
}
} }
.hiddenToolbar()
.windowBackgroundStyle(.thinMaterial)
.onAppear { .onAppear {
selectedInstruction = instructions.gettingStarted selectedInstruction = instructions.gettingStarted
} }
.frame(minHeight: 500) .frame(minWidth: 400, minHeight: 400)
}
}
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)
}
}
} }
} }

View File

@@ -85,7 +85,10 @@ struct SetupView: View {
integrations = true integrations = true
}, content: { }, content: {
IntegrationsView() 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) .frame(width: 20)
VStack(alignment: .leading, spacing: 4) { VStack(alignment: .leading, spacing: 4) {
Text(title) Text(title)
.fixedSize(horizontal: false, vertical: true)
.bold() .bold()
Text(description) Text(description)
.fixedSize(horizontal: false, vertical: true)
if let detail { if let detail {
Text(detail) Text(detail)
.fixedSize(horizontal: false, vertical: true)
.font(.callout) .font(.callout)
.italic() .italic()
} }

View File

@@ -32,6 +32,7 @@ struct ToolConfigurationView: View {
selectedSecret = created selectedSecret = created
} }
} }
.fixedSize()
} }
} }
} }

View File

@@ -24,7 +24,7 @@ extension View {
} }
struct MenuButtonModifier: ViewModifier { struct ToolbarCircleButtonModifier: ViewModifier {
func body(content: Content) -> some View { func body(content: Content) -> some View {
if #available(macOS 26.0, *) { if #available(macOS 26.0, *) {
@@ -40,8 +40,8 @@ struct MenuButtonModifier: ViewModifier {
extension View { extension View {
func menuButton() -> some View { func toolbarCircleButton() -> some View {
modifier(MenuButtonModifier()) modifier(ToolbarCircleButtonModifier())
} }
} }

View File

@@ -1,6 +1,6 @@
import SwiftUI import SwiftUI
struct ToolbarButtonStyle: ButtonStyle { struct ToolbarStatusButtonStyle: ButtonStyle {
private let lightColor: Color private let lightColor: Color
private let darkColor: Color private let darkColor: Color
@@ -39,6 +39,7 @@ struct ToolbarButtonStyle: ButtonStyle {
} else { } else {
configuration configuration
.label .label
.padding(EdgeInsets(top: 6, leading: 8, bottom: 6, trailing: 8))
.background(colorScheme == .light ? lightColor : darkColor) .background(colorScheme == .light ? lightColor : darkColor)
.foregroundColor(.white) .foregroundColor(.white)
.clipShape(RoundedRectangle(cornerRadius: 5)) .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))
}
}
}

View File

@@ -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())
}
}

View File

@@ -75,10 +75,23 @@ struct CreateSecretView<StoreType: SecretStoreModifiable>: View {
Section { Section {
VStack { VStack {
Picker(.createSecretKeyTypeLabel, selection: $keyType) { Picker(.createSecretKeyTypeLabel, selection: $keyType) {
ForEach(store.supportedKeyTypes, id: \.self) { option in ForEach(store.supportedKeyTypes.available, id: \.self) { option in
Text(String(describing: option)) Text(String(describing: option))
.tag(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 { if keyType?.algorithm == .mldsa {
@@ -119,7 +132,7 @@ struct CreateSecretView<StoreType: SecretStoreModifiable>: View {
.padding() .padding()
} }
.onAppear { .onAppear {
keyType = store.supportedKeyTypes.first keyType = store.supportedKeyTypes.available.first
} }
.formStyle(.grouped) .formStyle(.grouped)
} }

View 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)
}

View File

@@ -6,19 +6,21 @@ import Brief
struct ContentView: View { struct ContentView: View {
@Binding var showingCreation: Bool
@Binding var runningSetup: Bool
@Binding var hasRunSetup: Bool
@State var showingAgentInfo = false
@State var activeSecret: AnySecret? @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? @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 showingAppPathNotice = false
@State private var runningSetup = false
@State private var showingAgentInfo = false
var body: some View { var body: some View {
VStack { VStack {
@@ -35,6 +37,23 @@ struct ContentView: View {
toolbarItem(appPathNoticeView, id: "appPath") toolbarItem(appPathNoticeView, id: "appPath")
toolbarItem(newItemView, id: "new") 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) { .sheet(isPresented: $runningSetup) {
SetupView(setupComplete: $hasRunSetup) SetupView(setupComplete: $hasRunSetup)
} }
@@ -85,27 +104,12 @@ extension ContentView {
.font(.headline) .font(.headline)
.foregroundColor(.white) .foregroundColor(.white)
}) })
.buttonStyle(ToolbarButtonStyle(color: color)) .buttonStyle(ToolbarStatusButtonStyle(color: color))
.sheet(item: $selectedUpdate) { update in .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)
} }
} }
} }
}
@ViewBuilder @ViewBuilder
var newItemView: some View { var newItemView: some View {
@@ -113,16 +117,7 @@ extension ContentView {
Button(.appMenuNewSecretButton, systemImage: "plus") { Button(.appMenuNewSecretButton, systemImage: "plus") {
showingCreation = true showingCreation = true
} }
.menuButton() .toolbarCircleButton()
.sheet(isPresented: $showingCreation) {
if let modifiable = storeList.modifiableStore {
CreateSecretView(store: modifiable) { created in
if let created {
activeSecret = created
}
}
}
}
} }
} }
@@ -149,7 +144,7 @@ extension ContentView {
} }
}) })
.buttonStyle( .buttonStyle(
ToolbarButtonStyle( ToolbarStatusButtonStyle(
lightColor: agentStatusChecker.running ? .black.opacity(0.05) : .red.opacity(0.75), lightColor: agentStatusChecker.running ? .black.opacity(0.05) : .red.opacity(0.75),
darkColor: agentStatusChecker.running ? .white.opacity(0.05) : .red.opacity(0.5), darkColor: agentStatusChecker.running ? .white.opacity(0.05) : .red.opacity(0.5),
) )
@@ -171,18 +166,18 @@ extension ContentView {
.font(.headline) .font(.headline)
.foregroundColor(.white) .foregroundColor(.white)
}) })
.buttonStyle(ToolbarButtonStyle(color: .orange)) .buttonStyle(ToolbarStatusButtonStyle(color: .orange))
.popover(isPresented: $showingAppPathNotice, attachmentAnchor: attachmentAnchor, arrowEdge: .bottom) { .confirmationDialog(.appNotInApplicationsNoticeTitle, isPresented: $showingAppPathNotice) {
VStack { Button(.appNotInApplicationsNoticeCancelButton, role: .cancel) {
Image(systemName: "exclamationmark.triangle") }
.resizable() Button(.appNotInApplicationsNoticeQuitButton) {
.aspectRatio(contentMode: .fit) NSWorkspace.shared.selectFile(Bundle.main.bundlePath, inFileViewerRootedAtPath: Bundle.main.bundlePath)
.frame(width: 64) NSApplication.shared.terminate(nil)
}
} message: {
Text(.appNotInApplicationsNoticeDetailDescription) Text(.appNotInApplicationsNoticeDetailDescription)
.frame(maxWidth: 300)
}
.padding()
} }
.dialogIcon(Image(systemName: "folder.fill.badge.questionmark"))
} }
} }

View File

@@ -11,7 +11,7 @@ struct CopyableView: View {
@State private var interactionState: InteractionState = .normal @State private var interactionState: InteractionState = .normal
var content: some View { var content: some View {
VStack(alignment: .leading) { VStack(alignment: .leading, spacing: 15) {
HStack { HStack {
image image
.renderingMode(.template) .renderingMode(.template)
@@ -31,17 +31,16 @@ struct CopyableView: View {
.foregroundColor(secondaryTextColor) .foregroundColor(secondaryTextColor)
.transition(.opacity) .transition(.opacity)
} }
} }
.padding(EdgeInsets(top: 20, leading: 20, bottom: 10, trailing: 20))
Divider() Divider()
.ignoresSafeArea()
Text(text) Text(text)
.fixedSize(horizontal: false, vertical: true) .fixedSize(horizontal: false, vertical: true)
.foregroundColor(primaryTextColor) .foregroundColor(primaryTextColor)
.padding(EdgeInsets(top: 10, leading: 20, bottom: 20, trailing: 20))
.multilineTextAlignment(.leading) .multilineTextAlignment(.leading)
.font(.system(.body, design: .monospaced)) .font(.system(.body, design: .monospaced))
} }
.safeAreaPadding(20)
._background(interactionState: interactionState) ._background(interactionState: interactionState)
.frame(minWidth: 150, maxWidth: .infinity) .frame(minWidth: 150, maxWidth: .infinity)
} }
@@ -53,12 +52,12 @@ struct CopyableView: View {
interactionState = hovering ? .hovering : .normal interactionState = hovering ? .hovering : .normal
} }
} }
.onDrag({ .draggable(text) {
NSItemProvider(item: NSData(data: text.data(using: .utf8)!), typeIdentifier: UTType.utf8PlainText.identifier)
}, preview: {
content content
.lineLimit(3)
.frame(maxWidth: 300)
._background(interactionState: .dragging) ._background(interactionState: .dragging)
}) }
.onTapGesture { .onTapGesture {
copy() copy()
withAnimation { withAnimation {
@@ -79,7 +78,7 @@ struct CopyableView: View {
var copyButton: some View { var copyButton: some View {
switch interactionState { switch interactionState {
case .hovering: case .hovering:
Button(.copyableClickToCopyButton, systemImage: "document.on.document") { Button(.copyableClickToCopyButton, systemImage: "doc.on.doc") {
withAnimation { withAnimation {
// Button will eat the click, so we set interaction state manually. // Button will eat the click, so we set interaction state manually.
interactionState = .clicking interactionState = .clicking
@@ -159,7 +158,8 @@ fileprivate struct BackgroundViewModifier: ViewModifier {
// Very thin opacity lets user hover anywhere over the view, glassEffect doesn't allow. // Very thin opacity lets user hover anywhere over the view, glassEffect doesn't allow.
.background(.white.opacity(0.01), in: RoundedRectangle(cornerRadius: 15)) .background(.white.opacity(0.01), in: RoundedRectangle(cornerRadius: 15))
.glassEffect(.regular.tint(backgroundColor(interactionState: interactionState)), 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 { } else {
content content
.background(backgroundColor(interactionState: interactionState)) .background(backgroundColor(interactionState: interactionState))
@@ -170,21 +170,36 @@ fileprivate struct BackgroundViewModifier: ViewModifier {
func backgroundColor(interactionState: InteractionState) -> Color { func backgroundColor(interactionState: InteractionState) -> Color {
guard appearsActive else { return Color.clear } guard appearsActive else { return Color.clear }
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 { switch interactionState {
case .normal: case .normal:
return colorScheme == .dark ? Color(white: 0.2) : Color(white: 0.885) return colorScheme == .dark ? Color(white: 0.2) : Color(white: 0.885)
case .hovering, .dragging: case .hovering:
return colorScheme == .dark ? Color(white: 0.275) : Color(white: 0.82) return colorScheme == .dark ? Color(white: 0.275) : Color(white: 0.82)
case .clicking: case .clicking, .dragging:
return .accentColor return .accentColor
} }
} }
}
} }
#Preview { #Preview {
VStack {
CopyableView(title: .secretDetailSha256FingerprintLabel, image: Image(systemName: "figure.wave"), text: "Hello world.") CopyableView(title: .secretDetailSha256FingerprintLabel, image: Image(systemName: "figure.wave"), text: "Hello world.")
CopyableView(title: .secretDetailSha256FingerprintLabel, image: Image(systemName: "figure.wave"), text: "Hello world.")
}
.padding() .padding()
} }

View File

@@ -3,18 +3,13 @@ import Brief
struct UpdateDetailView: View { struct UpdateDetailView: View {
@Environment(\.updater) var updater: any UpdaterProtocol @Environment(\.updater) var updater
@Environment(\.openURL) var openURL
let update: Release let update: Release
var body: some View { var body: some View {
VStack { VStack(spacing: 0) {
Text(.updateVersionName(updateName: update.name)).font(.title)
GroupBox(label: Text(.updateReleaseNotesTitle)) {
ScrollView {
attributedBody
}
}
HStack { HStack {
if !update.critical { if !update.critical {
Button(.updateIgnoreButton) { Button(.updateIgnoreButton) {
@@ -22,42 +17,36 @@ struct UpdateDetailView: View {
await updater.ignore(release: update) await updater.ignore(release: update)
} }
} }
.buttonStyle(ToolbarButtonStyle())
}
Spacer() 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) { Button(.updateUpdateButton) {
NSWorkspace.shared.open(update.html_url) openURL(update.html_url)
} }
.buttonStyle(ToolbarButtonStyle(tint: .accentColor))
.keyboardShortcut(.defaultAction) .keyboardShortcut(.defaultAction)
} }
}
.padding() .padding()
.frame(maxWidth: 500) Divider()
Form {
Section {
Text(update.attributedBody)
} header: {
Text(.updateVersionName(updateName: update.name)) .headerProminence(.increased)
} }
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 { .formStyle(.grouped)
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"))
}

View File

@@ -11,7 +11,9 @@ final class SecretiveUpdater: NSObject, XPCProtocol {
func process(_: Data) async throws -> [Release] { func process(_: Data) async throws -> [Release] {
let (data, _) = try await URLSession.shared.data(from: Constants.updateURL) 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)
} }
} }