diff --git a/.cursor/commands/deslop.md b/.cursor/commands/deslop.md
new file mode 100644
index 0000000..b2bf165
--- /dev/null
+++ b/.cursor/commands/deslop.md
@@ -0,0 +1,12 @@
+# Remove AI code slop
+
+Check the diff against main, and remove all AI generated slop introduced in this branch.
+
+This includes:
+- Extra comments that a human wouldn't add or is inconsistent with the rest of the file
+- Extra defensive checks or try/catch blocks that are abnormal for that area of the codebase (especially if called by trusted / validated codepaths)
+- Casts to any to get around type issues
+- Deeply nested code that should be refactored using early return guards to improve readability
+- Any other style that is inconsistent with the file
+
+Report at the end with only a 1-3 sentence summary of what you changed.
diff --git a/.github/scripts/signing.sh b/.github/scripts/signing.sh
deleted file mode 100755
index b4799e5..0000000
--- a/.github/scripts/signing.sh
+++ /dev/null
@@ -1,22 +0,0 @@
-#!/bin/bash
-
-# Import certificate and private key
-echo $SIGNING_DATA | base64 -d -o Signing.p12
-security create-keychain -p ci ci.keychain
-security default-keychain -s ci.keychain
-security list-keychains -s ci.keychain
-security import ./Signing.p12 -k ci.keychain -P $SIGNING_PASSWORD -A
-security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k ci ci.keychain
-
-# Import Profiles
-mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
-echo $HOST_PROFILE_DATA | base64 -d -o Host.provisionprofile
-HOST_UUID=`grep UUID -A1 -a Host.provisionprofile | grep -io "[-A-F0-9]\{36\}"`
-cp Host.provisionprofile ~/Library/MobileDevice/Provisioning\ Profiles/$HOST_UUID.provisionprofile
-echo $AGENT_PROFILE_DATA | base64 -d -o Agent.provisionprofile
-AGENT_UUID=`grep UUID -A1 -a Agent.provisionprofile | grep -io "[-A-F0-9]\{36\}"`
-cp Agent.provisionprofile ~/Library/MobileDevice/Provisioning\ Profiles/$AGENT_UUID.provisionprofile
-
-# Create directories for ASC key
-mkdir ~/.private_keys
-echo -n "$APPLE_API_KEY_DATA" > ~/.private_keys/AuthKey_$APPLE_API_KEY_ID.p8
diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml
index f90208a..7fe7576 100644
--- a/.github/workflows/nightly.yml
+++ b/.github/workflows/nightly.yml
@@ -23,7 +23,22 @@ jobs:
AGENT_PROFILE_DATA: ${{ secrets.AGENT_PROFILE_DATA }}
APPLE_API_KEY_DATA: ${{ secrets.APPLE_API_KEY_DATA }}
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
- run: ./.github/scripts/signing.sh
+ run: |
+ echo $SIGNING_DATA | base64 -d -o Signing.p12
+ security create-keychain -p ci ci.keychain
+ security default-keychain -s ci.keychain
+ security list-keychains -s ci.keychain
+ security import ./Signing.p12 -k ci.keychain -P $SIGNING_PASSWORD -A
+ security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k ci ci.keychain
+ mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
+ echo $HOST_PROFILE_DATA | base64 -d -o Host.provisionprofile
+ HOST_UUID=$(grep UUID -A1 -a Host.provisionprofile | grep -io "[-A-F0-9]\{36\}")
+ cp Host.provisionprofile ~/Library/MobileDevice/Provisioning\ Profiles/$HOST_UUID.provisionprofile
+ echo $AGENT_PROFILE_DATA | base64 -d -o Agent.provisionprofile
+ AGENT_UUID=$(grep UUID -A1 -a Agent.provisionprofile | grep -io "[-A-F0-9]\{36\}")
+ cp Agent.provisionprofile ~/Library/MobileDevice/Provisioning\ Profiles/$AGENT_UUID.provisionprofile
+ mkdir ~/.private_keys
+ echo -n "$APPLE_API_KEY_DATA" > ~/.private_keys/AuthKey_$APPLE_API_KEY_ID.p8
- name: Set Environment
run: sudo xcrun xcode-select -s /Applications/Xcode_26.1.app
- name: Update Build Number
@@ -34,32 +49,70 @@ jobs:
sed -i '' -e "s/GITHUB_CI_VERSION/0.0.0_nightly-$DATE/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/Config/Config.xcconfig
- - name: Build
+ - name: Build App
run: xcrun xcodebuild -project Sources/Secretive.xcodeproj -scheme Secretive -configuration Release -archivePath Archive.xcarchive archive
- - name: Move to Artifact Folder
- run: mkdir Artifact; cp -r Archive.xcarchive/Products/Applications/Secretive.app Artifact
- - name: Upload App to Artifacts
- id: upload
- uses: actions/upload-artifact@v4
- with:
- name: Secretive
- path: Artifact
- - name: Download Zipped Artifact
- id: download
- env:
- ZIP_ID: ${{ steps.upload.outputs.artifact-id }}
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ - name: Build CLI
+ run: swift build -c release --product SecretiveCLI --package-path Sources/Packages
+ - name: Codesign CLI
run: |
- curl -L -H "Authorization: Bearer $GITHUB_TOKEN" -L \
- https://api.github.com/repos/maxgoedjen/secretive/actions/artifacts/$ZIP_ID/zip > Secretive.zip
+ CLI_BINARY="Sources/Packages/.build/release/SecretiveCLI"
+ ENTITLEMENTS="Sources/Packages/Sources/SecretiveCLI/SecretiveCLI.entitlements"
+ IDENTITY=$(security find-identity -p codesigning -v 2>/dev/null | grep "Developer ID Application" | head -n1 | awk -F'"' '{print $2}')
+ codesign --force --options runtime --sign "$IDENTITY" --identifier "com.maxgoedjen.Secretive.Host" --entitlements "$ENTITLEMENTS" "$CLI_BINARY"
+ - name: Prepare Artifact Folder
+ run: |
+ mkdir -p Artifact/App
+ mkdir -p Artifact/CLI
+ cp -r Archive.xcarchive/Products/Applications/Secretive.app Artifact/App/
+ cp Sources/Packages/.build/release/SecretiveCLI Artifact/CLI/secretive
+ - name: Build Installer Package
+ run: |
+ pkgbuild --root Artifact/App --install-location /Applications --identifier com.maxgoedjen.Secretive.app --version 1.0 App.pkg
+ pkgbuild --root Artifact/CLI --install-location /usr/local/bin --identifier com.maxgoedjen.Secretive.cli --version 1.0 CLI.pkg
+ cat > distribution.xml << 'EOF'
+
+
+ Secretive
+ com.maxgoedjen
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ App.pkg
+ CLI.pkg
+
+ EOF
+ productbuild --distribution distribution.xml --package-path . Secretive-unsigned.pkg
+ INSTALLER_IDENTITY=$(security find-identity -p basic -v 2>/dev/null | grep "Developer ID Installer" | head -n1 | awk -F'"' '{print $2}')
+ productsign --sign "$INSTALLER_IDENTITY" Secretive-unsigned.pkg Secretive.pkg
- name: Notarize
env:
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
- run: xcrun notarytool submit --key ~/.private_keys/AuthKey_$APPLE_API_KEY_ID.p8 --key-id $APPLE_API_KEY_ID --issuer $APPLE_API_ISSUER Secretive.zip
+ run: |
+ xcrun notarytool submit --key ~/.private_keys/AuthKey_$APPLE_API_KEY_ID.p8 --key-id $APPLE_API_KEY_ID --issuer $APPLE_API_ISSUER --wait Secretive.pkg
+ xcrun stapler staple Secretive.pkg
+ - name: Upload Installer to Artifacts
+ id: upload
+ uses: actions/upload-artifact@v4
+ with:
+ name: Secretive.pkg
+ path: Secretive.pkg
- name: Attest
id: attest
uses: actions/attest-build-provenance@v2
with:
- subject-name: "Secretive.zip"
- subject-digest: sha256:${{ steps.upload.outputs.artifact-digest }}
+ subject-path: "Secretive.pkg"
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 77aebf4..dca11bf 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -20,7 +20,22 @@ jobs:
AGENT_PROFILE_DATA: ${{ secrets.AGENT_PROFILE_DATA }}
APPLE_API_KEY_DATA: ${{ secrets.APPLE_API_KEY_DATA }}
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
- run: ./.github/scripts/signing.sh
+ run: |
+ echo $SIGNING_DATA | base64 -d -o Signing.p12
+ security create-keychain -p ci ci.keychain
+ security default-keychain -s ci.keychain
+ security list-keychains -s ci.keychain
+ security import ./Signing.p12 -k ci.keychain -P $SIGNING_PASSWORD -A
+ security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k ci ci.keychain
+ mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
+ echo $HOST_PROFILE_DATA | base64 -d -o Host.provisionprofile
+ HOST_UUID=$(grep UUID -A1 -a Host.provisionprofile | grep -io "[-A-F0-9]\{36\}")
+ cp Host.provisionprofile ~/Library/MobileDevice/Provisioning\ Profiles/$HOST_UUID.provisionprofile
+ echo $AGENT_PROFILE_DATA | base64 -d -o Agent.provisionprofile
+ AGENT_UUID=$(grep UUID -A1 -a Agent.provisionprofile | grep -io "[-A-F0-9]\{36\}")
+ cp Agent.provisionprofile ~/Library/MobileDevice/Provisioning\ Profiles/$AGENT_UUID.provisionprofile
+ mkdir ~/.private_keys
+ echo -n "$APPLE_API_KEY_DATA" > ~/.private_keys/AuthKey_$APPLE_API_KEY_ID.p8
- name: Set Environment
run: sudo xcrun xcode-select -s /Applications/Xcode_26.1.app
- name: Test
@@ -45,7 +60,22 @@ jobs:
AGENT_PROFILE_DATA: ${{ secrets.AGENT_PROFILE_DATA }}
APPLE_API_KEY_DATA: ${{ secrets.APPLE_API_KEY_DATA }}
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
- run: ./.github/scripts/signing.sh
+ run: |
+ echo $SIGNING_DATA | base64 -d -o Signing.p12
+ security create-keychain -p ci ci.keychain
+ security default-keychain -s ci.keychain
+ security list-keychains -s ci.keychain
+ security import ./Signing.p12 -k ci.keychain -P $SIGNING_PASSWORD -A
+ security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k ci ci.keychain
+ mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
+ echo $HOST_PROFILE_DATA | base64 -d -o Host.provisionprofile
+ HOST_UUID=$(grep UUID -A1 -a Host.provisionprofile | grep -io "[-A-F0-9]\{36\}")
+ cp Host.provisionprofile ~/Library/MobileDevice/Provisioning\ Profiles/$HOST_UUID.provisionprofile
+ echo $AGENT_PROFILE_DATA | base64 -d -o Agent.provisionprofile
+ AGENT_UUID=$(grep UUID -A1 -a Agent.provisionprofile | grep -io "[-A-F0-9]\{36\}")
+ cp Agent.provisionprofile ~/Library/MobileDevice/Provisioning\ Profiles/$AGENT_UUID.provisionprofile
+ mkdir ~/.private_keys
+ echo -n "$APPLE_API_KEY_DATA" > ~/.private_keys/AuthKey_$APPLE_API_KEY_ID.p8
- name: Set Environment
run: sudo xcrun xcode-select -s /Applications/Xcode_26.1.app
- name: Update Build Number
@@ -57,34 +87,73 @@ jobs:
sed -i '' -e "s/GITHUB_CI_VERSION/$CLEAN_TAG/g" Sources/Config/Config.xcconfig
sed -i '' -e "s/GITHUB_BUILD_NUMBER/1.$RUN_ID/g" Sources/Config/Config.xcconfig
sed -i '' -e "s/GITHUB_BUILD_URL/github.com\/maxgoedjen\/secretive\/actions\/runs\/$RUN_ID/g" Sources/Config/Config.xcconfig
- - name: Build
+ - name: Build App
run: xcrun xcodebuild -project Sources/Secretive.xcodeproj -scheme Secretive -configuration Release -archivePath Archive.xcarchive archive
- - name: Move to Artifact Folder
- run: mkdir Artifact; cp -r Archive.xcarchive/Products/Applications/Secretive.app Artifact
- - name: Upload App to Artifacts
- id: upload
- uses: actions/upload-artifact@v4
- with:
- name: Secretive.zip
- path: Artifact
- - name: Download Zipped Artifact
- id: download
- env:
- ZIP_ID: ${{ steps.upload.outputs.artifact-id }}
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ - name: Build CLI
+ run: swift build -c release --product SecretiveCLI --package-path Sources/Packages
+ - name: Codesign CLI
run: |
- curl -L -H "Authorization: Bearer $GITHUB_TOKEN" -L \
- https://api.github.com/repos/maxgoedjen/secretive/actions/artifacts/$ZIP_ID/zip > Secretive.zip
+ CLI_BINARY="Sources/Packages/.build/release/SecretiveCLI"
+ ENTITLEMENTS="Sources/Packages/Sources/SecretiveCLI/SecretiveCLI.entitlements"
+ IDENTITY=$(security find-identity -p codesigning -v 2>/dev/null | grep "Developer ID Application" | head -n1 | awk -F'"' '{print $2}')
+ codesign --force --options runtime --sign "$IDENTITY" --identifier "com.maxgoedjen.Secretive.Host" --entitlements "$ENTITLEMENTS" "$CLI_BINARY"
+ - name: Prepare Artifact Folder
+ run: |
+ mkdir -p Artifact/App
+ mkdir -p Artifact/CLI
+ cp -r Archive.xcarchive/Products/Applications/Secretive.app Artifact/App/
+ cp Sources/Packages/.build/release/SecretiveCLI Artifact/CLI/secretive
+ - name: Build Installer Package
+ run: |
+ pkgbuild --root Artifact/App --install-location /Applications --identifier com.maxgoedjen.Secretive.app --version 1.0 App.pkg
+ pkgbuild --root Artifact/CLI --install-location /usr/local/bin --identifier com.maxgoedjen.Secretive.cli --version 1.0 CLI.pkg
+ cat > distribution.xml << 'EOF'
+
+
+ Secretive
+ com.maxgoedjen
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ App.pkg
+ CLI.pkg
+
+ EOF
+ productbuild --distribution distribution.xml --package-path . Secretive-unsigned.pkg
+ INSTALLER_IDENTITY=$(security find-identity -p basic -v 2>/dev/null | grep "Developer ID Installer" | head -n1 | awk -F'"' '{print $2}')
+ productsign --sign "$INSTALLER_IDENTITY" Secretive-unsigned.pkg Secretive.pkg
- name: Notarize
env:
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
- run: xcrun notarytool submit --key ~/.private_keys/AuthKey_$APPLE_API_KEY_ID.p8 --key-id $APPLE_API_KEY_ID --issuer $APPLE_API_ISSUER Secretive.zip
+ run: |
+ xcrun notarytool submit --key ~/.private_keys/AuthKey_$APPLE_API_KEY_ID.p8 --key-id $APPLE_API_KEY_ID --issuer $APPLE_API_ISSUER --wait Secretive.pkg
+ xcrun stapler staple Secretive.pkg
+ - name: Upload Installer to Artifacts
+ id: upload
+ uses: actions/upload-artifact@v4
+ with:
+ name: Secretive.pkg
+ path: Secretive.pkg
- name: Attest
id: attest
uses: actions/attest-build-provenance@v2
with:
- subject-path: "Secretive.zip"
+ subject-path: "Secretive.pkg"
- name: Create Release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -95,4 +164,4 @@ jobs:
sed -i.tmp "s/RUN_ID/$RUN_ID/g" .github/templates/release.md
sed -i.tmp "s/ATTESTATION_ID/$ATTESTATION_ID/g" .github/templates/release.md
gh release create $TAG_NAME -d -F .github/templates/release.md
- gh release upload $TAG_NAME Secretive.zip
+ gh release upload $TAG_NAME Secretive.pkg
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..104a6b4
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,89 @@
+# Creates a dev package containing the Secretive app and CLI
+# Usage: make
+
+PROJECT_DIR := $(abspath $(dir $(lastword $(MAKEFILE_LIST))))
+BUILD_DIR := $(PROJECT_DIR)/build
+
+ARCHIVE := $(BUILD_DIR)/Archive.xcarchive
+APP_BUNDLE := $(BUILD_DIR)/Secretive.app
+APP_ROOT := $(BUILD_DIR)/AppPayload
+APP_PKG := $(BUILD_DIR)/App.pkg
+CLI_BIN := $(BUILD_DIR)/SecretiveCLI
+CLI_ROOT := $(BUILD_DIR)/CLIPayload
+CLI_PKG := $(BUILD_DIR)/CLI.pkg
+DIST := $(BUILD_DIR)/distribution.xml
+FINAL_PKG := $(BUILD_DIR)/Secretive-dev-unsigned.pkg
+
+XCODEBUILD := xcodebuild -project $(PROJECT_DIR)/Sources/Secretive.xcodeproj
+
+.PHONY: all clean
+
+all: $(FINAL_PKG)
+ @echo "Built: $(FINAL_PKG)"
+
+$(ARCHIVE):
+ @mkdir -p $(BUILD_DIR)
+ $(XCODEBUILD) -scheme Secretive -configuration Release CODE_SIGNING_ALLOWED=NO CODE_SIGNING_REQUIRED=NO CODE_SIGN_IDENTITY="" -archivePath $(ARCHIVE) archive
+
+$(APP_BUNDLE): $(ARCHIVE)
+ @rm -rf $(APP_BUNDLE)
+ cp -R $(ARCHIVE)/Products/Applications/Secretive.app $(APP_BUNDLE)
+
+$(CLI_BIN):
+ @mkdir -p $(BUILD_DIR)
+ cd $(PROJECT_DIR)/Sources/Packages && xcodebuild -scheme SecretiveCLI -configuration Release \
+ -destination 'platform=macOS' CODE_SIGNING_ALLOWED=NO CODE_SIGNING_REQUIRED=NO CODE_SIGN_IDENTITY="" \
+ SYMROOT=$(BUILD_DIR)/xcode-cli build
+ cp $(BUILD_DIR)/xcode-cli/Release/SecretiveCLI $(CLI_BIN)
+ cp -R $(BUILD_DIR)/xcode-cli/Release/*.bundle $(BUILD_DIR)/ 2>/dev/null || true
+
+$(APP_ROOT): $(APP_BUNDLE)
+ @rm -rf $(APP_ROOT)
+ @mkdir -p $(APP_ROOT)
+ cp -R $(APP_BUNDLE) $(APP_ROOT)/
+
+$(CLI_ROOT): $(CLI_BIN)
+ @rm -rf $(CLI_ROOT)
+ @mkdir -p $(CLI_ROOT)
+ cp $(CLI_BIN) $(CLI_ROOT)/secretive
+
+$(APP_PKG): $(APP_ROOT)
+ pkgbuild --root $(APP_ROOT) --install-location /Applications --identifier com.maxgoedjen.Secretive.app --version 0.0.0-dev $(APP_PKG)
+
+$(CLI_PKG): $(CLI_ROOT)
+ pkgbuild --root $(CLI_ROOT) --install-location /usr/local/bin --identifier com.maxgoedjen.Secretive.cli --version 0.0.0-dev $(CLI_PKG)
+
+$(DIST):
+ @mkdir -p $(BUILD_DIR)
+ @printf '%s\n' \
+ '' \
+ '' \
+ ' Secretive (Dev)' \
+ ' com.maxgoedjen' \
+ ' ' \
+ ' ' \
+ ' ' \
+ ' ' \
+ ' ' \
+ ' ' \
+ ' ' \
+ ' ' \
+ ' ' \
+ ' ' \
+ ' ' \
+ ' ' \
+ ' ' \
+ ' ' \
+ ' ' \
+ ' ' \
+ ' ' \
+ ' App.pkg' \
+ ' CLI.pkg' \
+ '' \
+ > $(DIST)
+
+$(FINAL_PKG): $(APP_PKG) $(CLI_PKG) $(DIST)
+ productbuild --distribution $(DIST) --package-path $(BUILD_DIR) $(FINAL_PKG)
+
+clean:
+ rm -rf $(BUILD_DIR)
diff --git a/Sources/Packages/Package.swift b/Sources/Packages/Package.swift
index 44c0323..248b963 100644
--- a/Sources/Packages/Package.swift
+++ b/Sources/Packages/Package.swift
@@ -46,15 +46,21 @@ let package = Package(
dependencies: ["SecretKit", "SecureEnclaveSecretKit", "SmartCardSecretKit"],
swiftSettings: swiftSettings,
),
+ .target(
+ name: "Localizations",
+ dependencies: [],
+ path: "Sources/SecretiveCLI/Generated",
+ swiftSettings: swiftSettings,
+ ),
.target(
name: "SecureEnclaveSecretKit",
- dependencies: ["SecretKit"],
+ dependencies: ["SecretKit", "Localizations"],
resources: [localization],
swiftSettings: swiftSettings,
),
.target(
name: "SmartCardSecretKit",
- dependencies: ["SecretKit"],
+ dependencies: ["SecretKit", "Localizations"],
resources: [localization],
swiftSettings: swiftSettings,
),
@@ -93,9 +99,12 @@ let package = Package(
dependencies: [
"SecretAgentKit",
"SecureEnclaveSecretKit",
+ "SmartCardSecretKit",
"SecretKit",
"Common",
+ "Localizations",
],
+ exclude: ["Generated"],
swiftSettings: swiftSettings,
),
]
diff --git a/Sources/Packages/Sources/SecretiveCLI/Generated/Localizations.swift b/Sources/Packages/Sources/SecretiveCLI/Generated/Localizations.swift
new file mode 100644
index 0000000..137b4ae
--- /dev/null
+++ b/Sources/Packages/Sources/SecretiveCLI/Generated/Localizations.swift
@@ -0,0 +1,38 @@
+// Copyright Anysphere Inc.
+// Localization extensions for String.LocalizationValue
+// These are normally generated by Xcode from String Catalogs, but SPM doesn't do this automatically.
+
+import Foundation
+
+public extension String.LocalizationValue {
+
+ // MARK: - Store Names
+
+ static var secureEnclave: String.LocalizationValue {
+ "secure_enclave"
+ }
+
+ static var smartCard: String.LocalizationValue {
+ "smart_card"
+ }
+
+ // MARK: - Secret Names
+
+ static var unnamedSecret: String.LocalizationValue {
+ "unnamed_secret"
+ }
+
+ // MARK: - Authentication Context
+
+ static var authContextRequestDenyButton: String.LocalizationValue {
+ "auth_context_request_deny_button"
+ }
+
+ static func authContextRequestSignatureDescription(appName: String, secretName: String) -> String.LocalizationValue {
+ "auth_context_request_signature_description \(appName) \(secretName)"
+ }
+
+ static func authContextPersistForDuration(secretName: String, duration: String) -> String.LocalizationValue {
+ "auth_context_persist_for_duration \(secretName) \(duration)"
+ }
+}
diff --git a/Sources/Packages/Sources/SecretiveCLI/README.md b/Sources/Packages/Sources/SecretiveCLI/README.md
index 3967684..aa80aa6 100644
--- a/Sources/Packages/Sources/SecretiveCLI/README.md
+++ b/Sources/Packages/Sources/SecretiveCLI/README.md
@@ -2,23 +2,31 @@
A command-line interface for Secretive that provides full key management and SSH agent functionality, sharing the same keychain and socket path as the GUI application.
-## Building
+## Installation
-Build the CLI using Swift Package Manager:
+The CLI is distributed as part of the Secretive installer package (`.pkg`). When you install Secretive via the pkg installer, the CLI binary is automatically installed to `/usr/local/bin/secretive`.
+
+Download the latest release from [GitHub Releases](https://github.com/maxgoedjen/secretive/releases).
+
+## Building (Development)
+
+For local development, build the CLI using Swift Package Manager:
```bash
-swift build -c release --product SecretiveCLI
+swift build -c release --product SecretiveCLI --package-path Sources/Packages
```
-The binary will be located at `.build/release/SecretiveCLI`.
+The binary will be located at `Sources/Packages/.build/release/SecretiveCLI`.
-## Code Signing
+**Note:** Production builds and code signing are handled automatically by GitHub Actions. See `.github/workflows/release.yml` and `.github/workflows/nightly.yml` for details.
-To use the CLI with the same keychain access as the GUI app, you must sign it with the same bundle identifier and entitlements.
+## Code Signing (Development)
+
+For local development with keychain access, you must sign the CLI with the same bundle identifier and entitlements as the GUI app.
### Required Entitlements
-The CLI must be signed with entitlements that include the same keychain access group as the GUI app. Create an entitlements file (e.g., `SecretiveCLI.entitlements`) with:
+The CLI uses the entitlements file at `SecretiveCLI.entitlements`:
```xml
@@ -44,8 +52,8 @@ codesign --force \
--sign "Developer ID Application: YOUR_TEAM_NAME" \
--options runtime \
--identifier com.maxgoedjen.Secretive.Host \
- --entitlements SecretiveCLI.entitlements \
- .build/release/SecretiveCLI
+ --entitlements Sources/Packages/Sources/SecretiveCLI/SecretiveCLI.entitlements \
+ Sources/Packages/.build/release/SecretiveCLI
```
Replace `YOUR_TEAM_NAME` with your actual Developer ID or use your team's signing identity.
@@ -60,22 +68,22 @@ Install and manage the SSH agent as a launchd service:
```bash
# Install the agent as a launchd service
-secretive-cli agent install
+secretive agent install
# Start the agent
-secretive-cli agent start
+secretive agent start
# Check agent status
-secretive-cli agent status
+secretive agent status
# Stop the agent
-secretive-cli agent stop
+secretive agent stop
# Uninstall the agent
-secretive-cli agent uninstall
+secretive agent uninstall
# Run agent in foreground (for testing)
-secretive-cli agent run
+secretive agent run
```
### Key Management
@@ -84,19 +92,19 @@ Manage SSH keys stored in the Secure Enclave:
```bash
# Generate a new key
-secretive-cli key generate "My Key Name"
+secretive key generate "My Key Name"
# List all keys
-secretive-cli key list
+secretive key list
# Show public key for a specific key
-secretive-cli key show "My Key Name"
+secretive key show "My Key Name"
# Delete a key
-secretive-cli key delete "My Key Name"
+secretive key delete "My Key Name"
# Update key (note: attributes cannot be changed after creation)
-secretive-cli key update "My Key Name"
+secretive key update "My Key Name"
```
## Socket Path
@@ -126,4 +134,3 @@ This is achieved by signing the CLI with the same bundle identifier (`com.maxgoe
- Keys created via CLI will appear in the GUI app and vice versa
- The agent can be run either via launchd (recommended) or in foreground mode for testing
- All key operations require appropriate authentication (Touch ID, Apple Watch, or password) as configured per key
-
diff --git a/Sources/Packages/Sources/SecretiveCLI/SecretiveCLI.entitlements b/Sources/Packages/Sources/SecretiveCLI/SecretiveCLI.entitlements
new file mode 100644
index 0000000..246a51d
--- /dev/null
+++ b/Sources/Packages/Sources/SecretiveCLI/SecretiveCLI.entitlements
@@ -0,0 +1,13 @@
+
+
+
+
+ com.apple.security.smartcard
+
+ keychain-access-groups
+
+ $(AppIdentifierPrefix)com.maxgoedjen.Secretive
+
+
+
+
diff --git a/Sources/Packages/Sources/SecretiveCLI/main.swift b/Sources/Packages/Sources/SecretiveCLI/SecretiveCLI.swift
similarity index 95%
rename from Sources/Packages/Sources/SecretiveCLI/main.swift
rename to Sources/Packages/Sources/SecretiveCLI/SecretiveCLI.swift
index c70c334..69f47d0 100644
--- a/Sources/Packages/Sources/SecretiveCLI/main.swift
+++ b/Sources/Packages/Sources/SecretiveCLI/SecretiveCLI.swift
@@ -231,23 +231,25 @@ extension SecretiveCLI {
logger.info("SSH agent listening on \(socketPath)")
print("SSH agent running on \(socketPath)")
print("Set SSH_AUTH_SOCK=\(socketPath) to use this agent")
+
+ func handleSession(_ session: SocketController.Session) async {
+ do {
+ for await message in session.messages {
+ let request = try parser.parse(data: message)
+ let response = await agent.handle(request: request, provenance: session.provenance)
+ try await MainActor.run {
+ try session.write(response)
+ }
+ }
+ } catch {
+ logger.error("Session error: \(error.localizedDescription)")
+ try? session.close()
+ }
+ }
// Handle sessions
for await session in socket.sessions {
- Task {
- do {
- for await message in session.messages {
- let request = try parser.parse(data: message)
- let response = await agent.handle(request: request, provenance: session.provenance)
- try await MainActor.run {
- try session.write(response)
- }
- }
- } catch {
- logger.error("Session error: \(error.localizedDescription)")
- try? session.close()
- }
- }
+ await handleSession(session)
}
}