[object Object]

← back to Exo

app

a46f8c3cd134536ce00115eeb278c0bd7fe6e8bf · 2025-08-02 07:14:27 +0500 · Sami Khan

Co-authored-by: Alex Cheema <alexcheema123@gmail.com>

Files touched

Diff

commit a46f8c3cd134536ce00115eeb278c0bd7fe6e8bf
Author: Sami Khan <98742866+samiamjidkhan@users.noreply.github.com>
Date:   Sat Aug 2 07:14:27 2025 +0500

    app
    
    Co-authored-by: Alex Cheema <alexcheema123@gmail.com>
---
 .DS_Store                                          | Bin 0 -> 8196 bytes
 .github/workflows/build-macos-app.yml              | 198 ++++++++
 .gitignore                                         |   1 +
 app/exov2/.DS_Store                                | Bin 0 -> 6148 bytes
 app/exov2/exov2.xcodeproj/project.pbxproj          | 548 +++++++++++++++++++++
 .../project.xcworkspace/contents.xcworkspacedata   |   7 +
 .../UserInterfaceState.xcuserstate                 | Bin 0 -> 33984 bytes
 .../xcshareddata/xcschemes/exov2.xcscheme          | 109 ++++
 .../xcschemes/xcschememanagement.plist             |  32 ++
 .../Preview Assets.xcassets/Contents.json          |   6 +
 app/exov2/exov2/ProcessManager.swift               | 377 ++++++++++++++
 app/exov2/exov2/exov2.entitlements                 |  14 +
 app/exov2/exov2/exov2App.swift                     | 115 +++++
 app/exov2/exov2Tests/exov2Tests.swift              |  17 +
 app/exov2/exov2UITests/exov2UITests.swift          |  43 ++
 .../exov2UITests/exov2UITestsLaunchTests.swift     |  33 ++
 16 files changed, 1500 insertions(+)

diff --git a/.DS_Store b/.DS_Store
new file mode 100644
index 00000000..1ad06f76
Binary files /dev/null and b/.DS_Store differ
diff --git a/.github/workflows/build-macos-app.yml b/.github/workflows/build-macos-app.yml
new file mode 100644
index 00000000..b9f01998
--- /dev/null
+++ b/.github/workflows/build-macos-app.yml
@@ -0,0 +1,198 @@
+name: Build and Release Exo macOS App
+
+on:
+  push:
+    tags:
+      - 'v*' # Trigger only on version tags
+    branches:
+      - main # Also build on main branch for testing
+      - app-staging # Add app-staging for testing
+  pull_request:
+    branches:
+      - main # Test builds on PRs
+
+jobs:
+  build-exov2-macos:
+    runs-on: macos-15
+    steps:
+      - name: Checkout code
+        uses: actions/checkout@v4
+        with:
+          fetch-depth: 0
+
+      - name: Setup Rust (nightly)
+        uses: actions-rust-lang/setup-rust-toolchain@v1
+        with:
+          toolchain: nightly
+          components: rustfmt, clippy
+          default: true
+
+      - name: Set Rust toolchain override
+        run: |
+          rustup default nightly
+          cd rust && rustup override set nightly
+
+      - name: Install Go
+        uses: actions/setup-go@v5
+        with:
+          go-version: '1.21'
+
+      - name: Install Just
+        run: |
+          brew install just
+
+      - name: Install UV
+        uses: astral-sh/setup-uv@v6
+        with:
+          enable-cache: true
+          cache-dependency-glob: uv.lock
+
+      - name: Setup Python Environment
+        run: |
+          uv python install
+          uv sync --locked --all-extras
+
+      - name: Build Rust Components
+        env:
+          RUSTFLAGS: "-A unused-imports -A dead-code -A unreachable-code"
+        run: |
+          just build-all
+
+      - name: Install Python Bindings
+        run: |
+          uv pip install dist/exo_pyo3_bindings-*.whl
+
+      - name: Verify Python Environment
+        run: |
+          uv run python -c "import exo_pyo3_bindings; print('Python bindings installed successfully')"
+          uv run python -c "import master.main; print('Master module available')"
+          uv run python -c "import worker.main; print('Worker module available')"
+
+      - name: Prepare Code Signing Keychain
+        if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
+        env:
+          MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
+          MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
+          PROVISIONING_PROFILE: ${{ secrets.PROVISIONING_PROFILE }}
+        run: |
+          security create-keychain -p "$MACOS_CERTIFICATE_PASSWORD" exov2.keychain
+          security default-keychain -s exov2.keychain
+          security unlock-keychain -p "$MACOS_CERTIFICATE_PASSWORD" exov2.keychain
+
+          echo "$MACOS_CERTIFICATE" | base64 --decode > /tmp/exov2-certificate.p12
+          security import /tmp/exov2-certificate.p12 -k exov2.keychain -P "$MACOS_CERTIFICATE_PASSWORD" -T /usr/bin/codesign
+          rm /tmp/exov2-certificate.p12
+          security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$MACOS_CERTIFICATE_PASSWORD" exov2.keychain
+
+          PROFILES_HOME="$HOME/Library/Developer/Xcode/UserData/Provisioning Profiles"
+          mkdir -p "$PROFILES_HOME"
+          PROFILE_PATH="$(mktemp "$PROFILES_HOME"/EXOV2_PP.provisionprofile)"
+          echo "$PROVISIONING_PROFILE" | base64 --decode > "$PROFILE_PATH"
+
+      - name: Build Exo Swift App
+        env:
+          MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
+        run: |
+          cd app/exov2
+          sudo xcode-select -s /Applications/Xcode.app/Contents/Developer
+          
+          if [[ "${{ github.event_name }}" == "push" && "${{ github.ref }}" == refs/tags/v* ]]; then
+            # Release build with code signing
+            security unlock-keychain -p "$MACOS_CERTIFICATE_PASSWORD" exov2.keychain
+            SIGNING_IDENTITY=$(security find-identity -v -p codesigning | awk -F '"' '{print $2}')
+            
+            xcodebuild clean build \
+              -project exov2.xcodeproj \
+              -scheme exov2 \
+              -configuration Release \
+              -derivedDataPath build \
+              CODE_SIGNING_IDENTITY="$SIGNING_IDENTITY" \
+              PROVISIONING_PROFILE_SPECIFIER="Exo Provisioning Profile" \
+              CODE_SIGN_INJECT_BASE_ENTITLEMENTS=YES \
+              OTHER_CODE_SIGN_FLAGS="--timestamp"
+          else
+            # Debug build without code signing for testing
+            xcodebuild clean build \
+              -project exov2.xcodeproj \
+              -scheme exov2 \
+              -configuration Debug \
+              -derivedDataPath build \
+              CODE_SIGN_IDENTITY="" \
+              CODE_SIGNING_REQUIRED=NO
+          fi
+
+          mv build/Build/Products/*/exov2.app ../../
+
+      - name: Sign and Create DMG (Release only)
+        if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
+        env:
+          APPLE_NOTARIZATION_USERNAME: ${{ secrets.APPLE_NOTARIZATION_USERNAME }}
+          APPLE_NOTARIZATION_PASSWORD: ${{ secrets.APPLE_NOTARIZATION_PASSWORD }}
+          APPLE_NOTARIZATION_TEAM: ${{ secrets.APPLE_NOTARIZATION_TEAM }}
+          MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
+        run: |
+          security unlock-keychain -p "$MACOS_CERTIFICATE_PASSWORD" exov2.keychain
+          SIGNING_IDENTITY=$(security find-identity -v -p codesigning | awk -F '"' '{print $2}')
+          
+          # Sign the app
+          /usr/bin/codesign --deep --force --timestamp --options runtime \
+            --sign "$SIGNING_IDENTITY" exov2.app
+          
+          # Verify the signing
+          codesign -dvv exov2.app
+          
+          # Create DMG
+          mkdir -p tmp/dmg-contents
+          cp -r ./exov2.app tmp/dmg-contents/
+          ln -s /Applications tmp/dmg-contents/Applications
+          VERSION=$(git describe --tags --abbrev=0 | sed 's/^v//')
+          
+          # Create and sign DMG
+          hdiutil create -volname "Exo" -srcfolder tmp/dmg-contents -ov -format UDZO exov2-${VERSION}.dmg
+          /usr/bin/codesign --deep --force --timestamp --options runtime \
+            --sign "$SIGNING_IDENTITY" exov2-${VERSION}.dmg
+
+          # Setup notarization credentials (optional - comment out if no notarization secrets)
+          if [[ -n "$APPLE_NOTARIZATION_USERNAME" ]]; then
+            xcrun notarytool store-credentials notary_pass \
+              --apple-id "$APPLE_NOTARIZATION_USERNAME" \
+              --password "$APPLE_NOTARIZATION_PASSWORD" \
+              --team-id "$APPLE_NOTARIZATION_TEAM"
+
+            # Submit for notarization
+            xcrun notarytool submit --wait \
+              --team-id "$APPLE_NOTARIZATION_TEAM" \
+              --keychain-profile notary_pass \
+              exov2-${VERSION}.dmg
+
+            # Staple the notarization
+            xcrun stapler staple exov2-${VERSION}.dmg
+          fi
+
+      - name: Create DMG (Debug builds)
+        if: github.event_name != 'push' || !startsWith(github.ref, 'refs/tags/v')
+        run: |
+          mkdir -p tmp/dmg-contents
+          cp -r ./exov2.app tmp/dmg-contents/
+          ln -s /Applications tmp/dmg-contents/Applications
+          VERSION=$(git rev-parse --short HEAD)
+          
+          hdiutil create -volname "Exo Debug" -srcfolder tmp/dmg-contents -ov -format UDZO exov2-debug-${VERSION}.dmg
+
+      - name: Cleanup Keychain
+        if: always() && github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
+        run: |
+          security default-keychain -s login.keychain
+          security delete-keychain exov2.keychain
+
+      - name: Upload DMG file
+        uses: actions/upload-artifact@v4
+        with:
+          name: exov2-dmg
+          path: exov2*.dmg
+
+      - name: Upload App Bundle
+        uses: actions/upload-artifact@v4
+        with:
+          name: exov2-app
+          path: exov2.app/ 
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index b3f86bdf..930ec3e1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,3 +11,4 @@ networking/target/
 networking/topology/target/
 
 build/
+*.xcuserstate
\ No newline at end of file
diff --git a/app/exov2/.DS_Store b/app/exov2/.DS_Store
new file mode 100644
index 00000000..31bdd033
Binary files /dev/null and b/app/exov2/.DS_Store differ
diff --git a/app/exov2/exov2.xcodeproj/project.pbxproj b/app/exov2/exov2.xcodeproj/project.pbxproj
new file mode 100644
index 00000000..a4e54fad
--- /dev/null
+++ b/app/exov2/exov2.xcodeproj/project.pbxproj
@@ -0,0 +1,548 @@
+// !$*UTF8*$!
+{
+	archiveVersion = 1;
+	classes = {
+	};
+	objectVersion = 77;
+	objects = {
+
+/* Begin PBXContainerItemProxy section */
+		E07D64CC2E36127F009BFB4D /* PBXContainerItemProxy */ = {
+			isa = PBXContainerItemProxy;
+			containerPortal = E07D64B22E36127E009BFB4D /* Project object */;
+			proxyType = 1;
+			remoteGlobalIDString = E07D64B92E36127E009BFB4D;
+			remoteInfo = exov2;
+		};
+		E07D64D62E36127F009BFB4D /* PBXContainerItemProxy */ = {
+			isa = PBXContainerItemProxy;
+			containerPortal = E07D64B22E36127E009BFB4D /* Project object */;
+			proxyType = 1;
+			remoteGlobalIDString = E07D64B92E36127E009BFB4D;
+			remoteInfo = exov2;
+		};
+/* End PBXContainerItemProxy section */
+
+/* Begin PBXFileReference section */
+		E07D64BA2E36127E009BFB4D /* exov2.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = exov2.app; sourceTree = BUILT_PRODUCTS_DIR; };
+		E07D64CB2E36127F009BFB4D /* exov2Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = exov2Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
+		E07D64D52E36127F009BFB4D /* exov2UITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = exov2UITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
+/* End PBXFileReference section */
+
+/* Begin PBXFileSystemSynchronizedRootGroup section */
+		E07D64BC2E36127E009BFB4D /* exov2 */ = {
+			isa = PBXFileSystemSynchronizedRootGroup;
+			path = exov2;
+			sourceTree = "<group>";
+		};
+		E07D64CE2E36127F009BFB4D /* exov2Tests */ = {
+			isa = PBXFileSystemSynchronizedRootGroup;
+			path = exov2Tests;
+			sourceTree = "<group>";
+		};
+		E07D64D82E36127F009BFB4D /* exov2UITests */ = {
+			isa = PBXFileSystemSynchronizedRootGroup;
+			path = exov2UITests;
+			sourceTree = "<group>";
+		};
+/* End PBXFileSystemSynchronizedRootGroup section */
+
+/* Begin PBXFrameworksBuildPhase section */
+		E07D64B72E36127E009BFB4D /* Frameworks */ = {
+			isa = PBXFrameworksBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+		E07D64C82E36127F009BFB4D /* Frameworks */ = {
+			isa = PBXFrameworksBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+		E07D64D22E36127F009BFB4D /* Frameworks */ = {
+			isa = PBXFrameworksBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+		E07D64B12E36127E009BFB4D = {
+			isa = PBXGroup;
+			children = (
+				E07D64BC2E36127E009BFB4D /* exov2 */,
+				E07D64CE2E36127F009BFB4D /* exov2Tests */,
+				E07D64D82E36127F009BFB4D /* exov2UITests */,
+				E07D64BB2E36127E009BFB4D /* Products */,
+			);
+			sourceTree = "<group>";
+		};
+		E07D64BB2E36127E009BFB4D /* Products */ = {
+			isa = PBXGroup;
+			children = (
+				E07D64BA2E36127E009BFB4D /* exov2.app */,
+				E07D64CB2E36127F009BFB4D /* exov2Tests.xctest */,
+				E07D64D52E36127F009BFB4D /* exov2UITests.xctest */,
+			);
+			name = Products;
+			sourceTree = "<group>";
+		};
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+		E07D64B92E36127E009BFB4D /* exov2 */ = {
+			isa = PBXNativeTarget;
+			buildConfigurationList = E07D64DF2E36127F009BFB4D /* Build configuration list for PBXNativeTarget "exov2" */;
+			buildPhases = (
+				E07D64B62E36127E009BFB4D /* Sources */,
+				E07D64B72E36127E009BFB4D /* Frameworks */,
+				E07D64B82E36127E009BFB4D /* Resources */,
+			);
+			buildRules = (
+			);
+			dependencies = (
+			);
+			fileSystemSynchronizedGroups = (
+				E07D64BC2E36127E009BFB4D /* exov2 */,
+			);
+			name = exov2;
+			packageProductDependencies = (
+			);
+			productName = exov2;
+			productReference = E07D64BA2E36127E009BFB4D /* exov2.app */;
+			productType = "com.apple.product-type.application";
+		};
+		E07D64CA2E36127F009BFB4D /* exov2Tests */ = {
+			isa = PBXNativeTarget;
+			buildConfigurationList = E07D64E22E36127F009BFB4D /* Build configuration list for PBXNativeTarget "exov2Tests" */;
+			buildPhases = (
+				E07D64C72E36127F009BFB4D /* Sources */,
+				E07D64C82E36127F009BFB4D /* Frameworks */,
+				E07D64C92E36127F009BFB4D /* Resources */,
+			);
+			buildRules = (
+			);
+			dependencies = (
+				E07D64CD2E36127F009BFB4D /* PBXTargetDependency */,
+			);
+			fileSystemSynchronizedGroups = (
+				E07D64CE2E36127F009BFB4D /* exov2Tests */,
+			);
+			name = exov2Tests;
+			packageProductDependencies = (
+			);
+			productName = exov2Tests;
+			productReference = E07D64CB2E36127F009BFB4D /* exov2Tests.xctest */;
+			productType = "com.apple.product-type.bundle.unit-test";
+		};
+		E07D64D42E36127F009BFB4D /* exov2UITests */ = {
+			isa = PBXNativeTarget;
+			buildConfigurationList = E07D64E52E36127F009BFB4D /* Build configuration list for PBXNativeTarget "exov2UITests" */;
+			buildPhases = (
+				E07D64D12E36127F009BFB4D /* Sources */,
+				E07D64D22E36127F009BFB4D /* Frameworks */,
+				E07D64D32E36127F009BFB4D /* Resources */,
+			);
+			buildRules = (
+			);
+			dependencies = (
+				E07D64D72E36127F009BFB4D /* PBXTargetDependency */,
+			);
+			fileSystemSynchronizedGroups = (
+				E07D64D82E36127F009BFB4D /* exov2UITests */,
+			);
+			name = exov2UITests;
+			packageProductDependencies = (
+			);
+			productName = exov2UITests;
+			productReference = E07D64D52E36127F009BFB4D /* exov2UITests.xctest */;
+			productType = "com.apple.product-type.bundle.ui-testing";
+		};
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+		E07D64B22E36127E009BFB4D /* Project object */ = {
+			isa = PBXProject;
+			attributes = {
+				BuildIndependentTargetsInParallel = 1;
+				LastSwiftUpdateCheck = 1610;
+				LastUpgradeCheck = 1610;
+				TargetAttributes = {
+					E07D64B92E36127E009BFB4D = {
+						CreatedOnToolsVersion = 16.1;
+					};
+					E07D64CA2E36127F009BFB4D = {
+						CreatedOnToolsVersion = 16.1;
+						TestTargetID = E07D64B92E36127E009BFB4D;
+					};
+					E07D64D42E36127F009BFB4D = {
+						CreatedOnToolsVersion = 16.1;
+						TestTargetID = E07D64B92E36127E009BFB4D;
+					};
+				};
+			};
+			buildConfigurationList = E07D64B52E36127E009BFB4D /* Build configuration list for PBXProject "exov2" */;
+			developmentRegion = en;
+			hasScannedForEncodings = 0;
+			knownRegions = (
+				en,
+				Base,
+			);
+			mainGroup = E07D64B12E36127E009BFB4D;
+			minimizedProjectReferenceProxies = 1;
+			preferredProjectObjectVersion = 77;
+			productRefGroup = E07D64BB2E36127E009BFB4D /* Products */;
+			projectDirPath = "";
+			projectRoot = "";
+			targets = (
+				E07D64B92E36127E009BFB4D /* exov2 */,
+				E07D64CA2E36127F009BFB4D /* exov2Tests */,
+				E07D64D42E36127F009BFB4D /* exov2UITests */,
+			);
+		};
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+		E07D64B82E36127E009BFB4D /* Resources */ = {
+			isa = PBXResourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+		E07D64C92E36127F009BFB4D /* Resources */ = {
+			isa = PBXResourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+		E07D64D32E36127F009BFB4D /* Resources */ = {
+			isa = PBXResourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+		E07D64B62E36127E009BFB4D /* Sources */ = {
+			isa = PBXSourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+		E07D64C72E36127F009BFB4D /* Sources */ = {
+			isa = PBXSourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+		E07D64D12E36127F009BFB4D /* Sources */ = {
+			isa = PBXSourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXTargetDependency section */
+		E07D64CD2E36127F009BFB4D /* PBXTargetDependency */ = {
+			isa = PBXTargetDependency;
+			target = E07D64B92E36127E009BFB4D /* exov2 */;
+			targetProxy = E07D64CC2E36127F009BFB4D /* PBXContainerItemProxy */;
+		};
+		E07D64D72E36127F009BFB4D /* PBXTargetDependency */ = {
+			isa = PBXTargetDependency;
+			target = E07D64B92E36127E009BFB4D /* exov2 */;
+			targetProxy = E07D64D62E36127F009BFB4D /* PBXContainerItemProxy */;
+		};
+/* End PBXTargetDependency section */
+
+/* Begin XCBuildConfiguration section */
+		E07D64DD2E36127F009BFB4D /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = NO;
+				ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
+				CLANG_ANALYZER_NONNULL = YES;
+				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
+				CLANG_ENABLE_MODULES = YES;
+				CLANG_ENABLE_OBJC_ARC = YES;
+				CLANG_ENABLE_OBJC_WEAK = YES;
+				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+				CLANG_WARN_BOOL_CONVERSION = YES;
+				CLANG_WARN_COMMA = YES;
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
+				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+				CLANG_WARN_EMPTY_BODY = YES;
+				CLANG_WARN_ENUM_CONVERSION = YES;
+				CLANG_WARN_INFINITE_RECURSION = YES;
+				CLANG_WARN_INT_CONVERSION = YES;
+				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+				CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+				CLANG_WARN_STRICT_PROTOTYPES = YES;
+				CLANG_WARN_SUSPICIOUS_MOVE = YES;
+				CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+				CLANG_WARN_UNREACHABLE_CODE = YES;
+				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+				COPY_PHASE_STRIP = NO;
+				DEBUG_INFORMATION_FORMAT = dwarf;
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
+				ENABLE_TESTABILITY = YES;
+				ENABLE_USER_SCRIPT_SANDBOXING = YES;
+				GCC_C_LANGUAGE_STANDARD = gnu17;
+				GCC_DYNAMIC_NO_PIC = NO;
+				GCC_NO_COMMON_BLOCKS = YES;
+				GCC_OPTIMIZATION_LEVEL = 0;
+				GCC_PREPROCESSOR_DEFINITIONS = (
+					"DEBUG=1",
+					"$(inherited)",
+				);
+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+				GCC_WARN_UNDECLARED_SELECTOR = YES;
+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+				GCC_WARN_UNUSED_FUNCTION = YES;
+				GCC_WARN_UNUSED_VARIABLE = YES;
+				LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
+				MACOSX_DEPLOYMENT_TARGET = 15.1;
+				MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
+				MTL_FAST_MATH = YES;
+				ONLY_ACTIVE_ARCH = YES;
+				SDKROOT = macosx;
+				SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
+				SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+			};
+			name = Debug;
+		};
+		E07D64DE2E36127F009BFB4D /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = NO;
+				ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
+				CLANG_ANALYZER_NONNULL = YES;
+				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
+				CLANG_ENABLE_MODULES = YES;
+				CLANG_ENABLE_OBJC_ARC = YES;
+				CLANG_ENABLE_OBJC_WEAK = YES;
+				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+				CLANG_WARN_BOOL_CONVERSION = YES;
+				CLANG_WARN_COMMA = YES;
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
+				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+				CLANG_WARN_EMPTY_BODY = YES;
+				CLANG_WARN_ENUM_CONVERSION = YES;
+				CLANG_WARN_INFINITE_RECURSION = YES;
+				CLANG_WARN_INT_CONVERSION = YES;
+				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+				CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+				CLANG_WARN_STRICT_PROTOTYPES = YES;
+				CLANG_WARN_SUSPICIOUS_MOVE = YES;
+				CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+				CLANG_WARN_UNREACHABLE_CODE = YES;
+				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+				COPY_PHASE_STRIP = NO;
+				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+				ENABLE_NS_ASSERTIONS = NO;
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
+				ENABLE_USER_SCRIPT_SANDBOXING = YES;
+				GCC_C_LANGUAGE_STANDARD = gnu17;
+				GCC_NO_COMMON_BLOCKS = YES;
+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+				GCC_WARN_UNDECLARED_SELECTOR = YES;
+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+				GCC_WARN_UNUSED_FUNCTION = YES;
+				GCC_WARN_UNUSED_VARIABLE = YES;
+				LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
+				MACOSX_DEPLOYMENT_TARGET = 15.1;
+				MTL_ENABLE_DEBUG_INFO = NO;
+				MTL_FAST_MATH = YES;
+				SDKROOT = macosx;
+				SWIFT_COMPILATION_MODE = wholemodule;
+			};
+			name = Release;
+		};
+		E07D64E02E36127F009BFB4D /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+				CODE_SIGN_ENTITLEMENTS = exov2/exov2.entitlements;
+				CODE_SIGN_STYLE = Automatic;
+				COMBINE_HIDPI_IMAGES = YES;
+				CURRENT_PROJECT_VERSION = 1;
+				DEVELOPMENT_ASSET_PATHS = "\"exov2/Preview Content\"";
+				ENABLE_PREVIEWS = YES;
+				GENERATE_INFOPLIST_FILE = YES;
+				INFOPLIST_KEY_LSUIElement = YES;
+				INFOPLIST_KEY_NSHumanReadableCopyright = "";
+				LD_RUNPATH_SEARCH_PATHS = (
+					"$(inherited)",
+					"@executable_path/../Frameworks",
+				);
+				MARKETING_VERSION = 1.0;
+				PRODUCT_BUNDLE_IDENTIFIER = exolabs.exov2;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				SWIFT_EMIT_LOC_STRINGS = YES;
+				SWIFT_VERSION = 5.0;
+			};
+			name = Debug;
+		};
+		E07D64E12E36127F009BFB4D /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+				CODE_SIGN_ENTITLEMENTS = exov2/exov2.entitlements;
+				CODE_SIGN_STYLE = Automatic;
+				COMBINE_HIDPI_IMAGES = YES;
+				CURRENT_PROJECT_VERSION = 1;
+				DEVELOPMENT_ASSET_PATHS = "\"exov2/Preview Content\"";
+				ENABLE_PREVIEWS = YES;
+				GENERATE_INFOPLIST_FILE = YES;
+				INFOPLIST_KEY_LSUIElement = YES;
+				INFOPLIST_KEY_NSHumanReadableCopyright = "";
+				LD_RUNPATH_SEARCH_PATHS = (
+					"$(inherited)",
+					"@executable_path/../Frameworks",
+				);
+				MARKETING_VERSION = 1.0;
+				PRODUCT_BUNDLE_IDENTIFIER = exolabs.exov2;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				SWIFT_EMIT_LOC_STRINGS = YES;
+				SWIFT_VERSION = 5.0;
+			};
+			name = Release;
+		};
+		E07D64E32E36127F009BFB4D /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				BUNDLE_LOADER = "$(TEST_HOST)";
+				CODE_SIGN_STYLE = Automatic;
+				CURRENT_PROJECT_VERSION = 1;
+				GENERATE_INFOPLIST_FILE = YES;
+				MACOSX_DEPLOYMENT_TARGET = 15.1;
+				MARKETING_VERSION = 1.0;
+				PRODUCT_BUNDLE_IDENTIFIER = exolabs.exov2Tests;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				SWIFT_EMIT_LOC_STRINGS = NO;
+				SWIFT_VERSION = 5.0;
+				TEST_HOST = "$(BUILT_PRODUCTS_DIR)/exov2.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/exov2";
+			};
+			name = Debug;
+		};
+		E07D64E42E36127F009BFB4D /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				BUNDLE_LOADER = "$(TEST_HOST)";
+				CODE_SIGN_STYLE = Automatic;
+				CURRENT_PROJECT_VERSION = 1;
+				GENERATE_INFOPLIST_FILE = YES;
+				MACOSX_DEPLOYMENT_TARGET = 15.1;
+				MARKETING_VERSION = 1.0;
+				PRODUCT_BUNDLE_IDENTIFIER = exolabs.exov2Tests;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				SWIFT_EMIT_LOC_STRINGS = NO;
+				SWIFT_VERSION = 5.0;
+				TEST_HOST = "$(BUILT_PRODUCTS_DIR)/exov2.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/exov2";
+			};
+			name = Release;
+		};
+		E07D64E62E36127F009BFB4D /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				CODE_SIGN_STYLE = Automatic;
+				CURRENT_PROJECT_VERSION = 1;
+				GENERATE_INFOPLIST_FILE = YES;
+				MARKETING_VERSION = 1.0;
+				PRODUCT_BUNDLE_IDENTIFIER = exolabs.exov2UITests;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				SWIFT_EMIT_LOC_STRINGS = NO;
+				SWIFT_VERSION = 5.0;
+				TEST_TARGET_NAME = exov2;
+			};
+			name = Debug;
+		};
+		E07D64E72E36127F009BFB4D /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				CODE_SIGN_STYLE = Automatic;
+				CURRENT_PROJECT_VERSION = 1;
+				GENERATE_INFOPLIST_FILE = YES;
+				MARKETING_VERSION = 1.0;
+				PRODUCT_BUNDLE_IDENTIFIER = exolabs.exov2UITests;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				SWIFT_EMIT_LOC_STRINGS = NO;
+				SWIFT_VERSION = 5.0;
+				TEST_TARGET_NAME = exov2;
+			};
+			name = Release;
+		};
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+		E07D64B52E36127E009BFB4D /* Build configuration list for PBXProject "exov2" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				E07D64DD2E36127F009BFB4D /* Debug */,
+				E07D64DE2E36127F009BFB4D /* Release */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+		E07D64DF2E36127F009BFB4D /* Build configuration list for PBXNativeTarget "exov2" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				E07D64E02E36127F009BFB4D /* Debug */,
+				E07D64E12E36127F009BFB4D /* Release */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+		E07D64E22E36127F009BFB4D /* Build configuration list for PBXNativeTarget "exov2Tests" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				E07D64E32E36127F009BFB4D /* Debug */,
+				E07D64E42E36127F009BFB4D /* Release */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+		E07D64E52E36127F009BFB4D /* Build configuration list for PBXNativeTarget "exov2UITests" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				E07D64E62E36127F009BFB4D /* Debug */,
+				E07D64E72E36127F009BFB4D /* Release */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+/* End XCConfigurationList section */
+	};
+	rootObject = E07D64B22E36127E009BFB4D /* Project object */;
+}
diff --git a/app/exov2/exov2.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/app/exov2/exov2.xcodeproj/project.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 00000000..919434a6
--- /dev/null
+++ b/app/exov2/exov2.xcodeproj/project.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Workspace
+   version = "1.0">
+   <FileRef
+      location = "self:">
+   </FileRef>
+</Workspace>
diff --git a/app/exov2/exov2.xcodeproj/project.xcworkspace/xcuserdata/samikhan.xcuserdatad/UserInterfaceState.xcuserstate b/app/exov2/exov2.xcodeproj/project.xcworkspace/xcuserdata/samikhan.xcuserdatad/UserInterfaceState.xcuserstate
new file mode 100644
index 00000000..92fe6a19
Binary files /dev/null and b/app/exov2/exov2.xcodeproj/project.xcworkspace/xcuserdata/samikhan.xcuserdatad/UserInterfaceState.xcuserstate differ
diff --git a/app/exov2/exov2.xcodeproj/xcshareddata/xcschemes/exov2.xcscheme b/app/exov2/exov2.xcodeproj/xcshareddata/xcschemes/exov2.xcscheme
new file mode 100644
index 00000000..b6afb5a7
--- /dev/null
+++ b/app/exov2/exov2.xcodeproj/xcshareddata/xcschemes/exov2.xcscheme
@@ -0,0 +1,109 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+   LastUpgradeVersion = "1610"
+   version = "1.7">
+   <BuildAction
+      parallelizeBuildables = "YES"
+      buildImplicitDependencies = "YES"
+      buildArchitectures = "Automatic">
+      <BuildActionEntries>
+         <BuildActionEntry
+            buildForTesting = "YES"
+            buildForRunning = "YES"
+            buildForProfiling = "YES"
+            buildForArchiving = "YES"
+            buildForAnalyzing = "YES">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "E07D64B92E36127E009BFB4D"
+               BuildableName = "exov2.app"
+               BlueprintName = "exov2"
+               ReferencedContainer = "container:exov2.xcodeproj">
+            </BuildableReference>
+         </BuildActionEntry>
+      </BuildActionEntries>
+   </BuildAction>
+   <TestAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      shouldUseLaunchSchemeArgsEnv = "YES"
+      shouldAutocreateTestPlan = "YES">
+      <Testables>
+         <TestableReference
+            skipped = "NO"
+            parallelizable = "YES">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "E07D64CA2E36127F009BFB4D"
+               BuildableName = "exov2Tests.xctest"
+               BlueprintName = "exov2Tests"
+               ReferencedContainer = "container:exov2.xcodeproj">
+            </BuildableReference>
+         </TestableReference>
+         <TestableReference
+            skipped = "NO"
+            parallelizable = "YES">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "E07D64D42E36127F009BFB4D"
+               BuildableName = "exov2UITests.xctest"
+               BlueprintName = "exov2UITests"
+               ReferencedContainer = "container:exov2.xcodeproj">
+            </BuildableReference>
+         </TestableReference>
+      </Testables>
+   </TestAction>
+   <LaunchAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      launchStyle = "0"
+      useCustomWorkingDirectory = "NO"
+      ignoresPersistentStateOnLaunch = "NO"
+      debugDocumentVersioning = "YES"
+      debugServiceExtension = "internal"
+      allowLocationSimulation = "YES">
+      <BuildableProductRunnable
+         runnableDebuggingMode = "0">
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "E07D64B92E36127E009BFB4D"
+            BuildableName = "exov2.app"
+            BlueprintName = "exov2"
+            ReferencedContainer = "container:exov2.xcodeproj">
+         </BuildableReference>
+      </BuildableProductRunnable>
+      <EnvironmentVariables>
+         <EnvironmentVariable
+            key = "EXO_PROJECT_ROOT"
+            value = "/Users/SamiKhan/exo"
+            isEnabled = "YES">
+         </EnvironmentVariable>
+      </EnvironmentVariables>
+   </LaunchAction>
+   <ProfileAction
+      buildConfiguration = "Release"
+      shouldUseLaunchSchemeArgsEnv = "YES"
+      savedToolIdentifier = ""
+      useCustomWorkingDirectory = "NO"
+      debugDocumentVersioning = "YES">
+      <BuildableProductRunnable
+         runnableDebuggingMode = "0">
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "E07D64B92E36127E009BFB4D"
+            BuildableName = "exov2.app"
+            BlueprintName = "exov2"
+            ReferencedContainer = "container:exov2.xcodeproj">
+         </BuildableReference>
+      </BuildableProductRunnable>
+   </ProfileAction>
+   <AnalyzeAction
+      buildConfiguration = "Debug">
+   </AnalyzeAction>
+   <ArchiveAction
+      buildConfiguration = "Release"
+      revealArchiveInOrganizer = "YES">
+   </ArchiveAction>
+</Scheme>
diff --git a/app/exov2/exov2.xcodeproj/xcuserdata/samikhan.xcuserdatad/xcschemes/xcschememanagement.plist b/app/exov2/exov2.xcodeproj/xcuserdata/samikhan.xcuserdatad/xcschemes/xcschememanagement.plist
new file mode 100644
index 00000000..f9edf8e6
--- /dev/null
+++ b/app/exov2/exov2.xcodeproj/xcuserdata/samikhan.xcuserdatad/xcschemes/xcschememanagement.plist
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>SchemeUserState</key>
+	<dict>
+		<key>exov2.xcscheme_^#shared#^_</key>
+		<dict>
+			<key>orderHint</key>
+			<integer>0</integer>
+		</dict>
+	</dict>
+	<key>SuppressBuildableAutocreation</key>
+	<dict>
+		<key>E07D64B92E36127E009BFB4D</key>
+		<dict>
+			<key>primary</key>
+			<true/>
+		</dict>
+		<key>E07D64CA2E36127F009BFB4D</key>
+		<dict>
+			<key>primary</key>
+			<true/>
+		</dict>
+		<key>E07D64D42E36127F009BFB4D</key>
+		<dict>
+			<key>primary</key>
+			<true/>
+		</dict>
+	</dict>
+</dict>
+</plist>
diff --git a/app/exov2/exov2/Preview Content/Preview Assets.xcassets/Contents.json b/app/exov2/exov2/Preview Content/Preview Assets.xcassets/Contents.json
new file mode 100644
index 00000000..73c00596
--- /dev/null
+++ b/app/exov2/exov2/Preview Content/Preview Assets.xcassets/Contents.json	
@@ -0,0 +1,6 @@
+{
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}
diff --git a/app/exov2/exov2/ProcessManager.swift b/app/exov2/exov2/ProcessManager.swift
new file mode 100644
index 00000000..81c5275a
--- /dev/null
+++ b/app/exov2/exov2/ProcessManager.swift
@@ -0,0 +1,377 @@
+import Foundation
+import OSLog
+import SwiftUI
+import AppKit
+import ServiceManagement
+
+extension NSApplication {
+    func addTerminationHandler(_ handler: @escaping () -> Void) {
+        NSApp.setActivationPolicy(.accessory)
+        NotificationCenter.default.addObserver(forName: NSApplication.willTerminateNotification,
+                                            object: nil,
+                                            queue: .main) { _ in
+            handler()
+        }
+    }
+}
+
+class ProcessManager: ObservableObject {
+    @Published var masterProcess: Process?
+    @Published var workerProcess: Process?
+    @Published var masterStatus: String = "Stopped"
+    @Published var workerStatus: String = "Stopped"
+    @Published var isLoginItemEnabled: Bool = false
+    @Published var isMasterMode: Bool = false  // Default to replica mode (false)
+    
+    private var masterStdout: Pipe?
+    private var workerStdout: Pipe?
+    private let logger = Logger(subsystem: "exolabs.exov2", category: "ProcessManager")
+    
+    // Add file handle properties to track them
+    private var masterFileHandle: FileHandle?
+    private var workerFileHandle: FileHandle?
+    
+    private let loginService = SMAppService.mainApp
+    
+    // Find uv executable in common installation paths
+    private var uvPath: String? {
+        let commonPaths = [
+            "/usr/local/bin/uv",
+            "/opt/homebrew/bin/uv",
+            "/usr/bin/uv",
+            "/bin/uv",
+            "/Users/\(NSUserName())/.cargo/bin/uv",
+            "/Users/\(NSUserName())/.local/bin/uv"
+        ]
+        
+        for path in commonPaths {
+            if FileManager.default.fileExists(atPath: path) {
+                return path
+            }
+        }
+        
+        // Try using 'which uv' command as fallback
+        let process = Process()
+        process.executableURL = URL(fileURLWithPath: "/usr/bin/which")
+        process.arguments = ["uv"]
+        
+        let pipe = Pipe()
+        process.standardOutput = pipe
+        process.standardError = Pipe()
+        
+        do {
+            try process.run()
+            process.waitUntilExit()
+            
+            if process.terminationStatus == 0 {
+                let data = pipe.fileHandleForReading.readDataToEndOfFile()
+                if let path = String(data: data, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines),
+                   !path.isEmpty {
+                    return path
+                }
+            }
+        } catch {
+            logger.error("Failed to run 'which uv': \(error.localizedDescription)")
+        }
+        
+        return nil
+    }
+    
+    // Project root path - assuming the app bundle is in the project directory
+    private var projectPath: URL? {
+        // Get the app bundle path and navigate to the project root
+        // This assumes the app is built/run from within the project directory
+        guard let bundlePath = Bundle.main.bundleURL.path as String? else { return nil }
+        
+        // Navigate up from the app bundle to find the project root
+        // Look for pyproject.toml to identify the project root
+        var currentPath = URL(fileURLWithPath: bundlePath)
+        while currentPath.pathComponents.count > 1 {
+            let pyprojectPath = currentPath.appendingPathComponent("pyproject.toml")
+            if FileManager.default.fileExists(atPath: pyprojectPath.path) {
+                return currentPath
+            }
+            currentPath = currentPath.deletingLastPathComponent()
+        }
+        
+        // Fallback: try to find project in common development locations
+        let homeDir = FileManager.default.homeDirectoryForCurrentUser
+        let commonPaths = [
+            "exo",
+            "Projects/exo", 
+            "Documents/exo",
+            "Desktop/exo"
+        ]
+        
+        for path in commonPaths {
+            let projectDir = homeDir.appendingPathComponent(path)
+            let pyprojectPath = projectDir.appendingPathComponent("pyproject.toml")
+            if FileManager.default.fileExists(atPath: pyprojectPath.path) {
+                return projectDir
+            }
+        }
+        
+        return nil
+    }
+    
+    init() {
+        // Add termination handler
+        NSApplication.shared.addTerminationHandler { [weak self] in
+            self?.stopAll()
+        }
+        
+        // Check if login item is enabled
+        isLoginItemEnabled = (loginService.status == .enabled)
+        
+        // Start processes automatically
+        startMaster()
+        DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
+            self.startWorker()
+        }
+    }
+    
+    private func handleProcessOutput(_ pipe: Pipe, processName: String) -> FileHandle {
+        let fileHandle = pipe.fileHandleForReading
+        fileHandle.readabilityHandler = { [weak self] handle in
+            guard let data = try? handle.read(upToCount: 1024),
+                  let output = String(data: data, encoding: .utf8) else {
+                return
+            }
+            
+            DispatchQueue.main.async {
+                self?.logger.info("\(processName) output: \(output)")
+                print("[\(processName)] \(output)")
+            }
+        }
+        return fileHandle
+    }
+    
+    private func cleanupProcess(process: Process?, fileHandle: FileHandle?, pipe: Pipe?) {
+        // Remove readability handler
+        fileHandle?.readabilityHandler = nil
+        
+        // Close file handles
+        try? fileHandle?.close()
+        try? pipe?.fileHandleForReading.close()
+        try? pipe?.fileHandleForWriting.close()
+        
+        // Terminate process if still running
+        if process?.isRunning == true {
+            process?.terminate()
+        }
+    }
+    
+    func startMaster() {
+        guard let projectPath = self.projectPath else {
+            masterStatus = "Error: Project directory not found"
+            logger.error("Could not find project directory with pyproject.toml")
+            return
+        }
+        
+        guard let uvPath = self.uvPath else {
+            masterStatus = "Error: uv not found"
+            logger.error("Could not find uv executable in common paths")
+            return
+        }
+        
+        // Cleanup any existing process
+        cleanupProcess(process: masterProcess, fileHandle: masterFileHandle, pipe: masterStdout)
+        
+        masterProcess = Process()
+        masterStdout = Pipe()
+        
+        // Use uv to run the master module
+        masterProcess?.executableURL = URL(fileURLWithPath: uvPath)
+        masterProcess?.arguments = ["run", "python", "-m", "master.main"]
+        masterProcess?.standardOutput = masterStdout
+        masterProcess?.standardError = masterStdout
+        
+        // Set up environment
+        var env = ProcessInfo.processInfo.environment
+        env["PYTHONUNBUFFERED"] = "1"
+        env["PYTHONPATH"] = projectPath.path
+        
+        // Set replica mode if not in master mode
+        if !self.isMasterMode {
+            env["EXO_RUN_AS_REPLICA"] = "1"
+        }
+        
+        masterProcess?.environment = env
+        
+        // Set working directory to project root
+        masterProcess?.currentDirectoryURL = projectPath
+        
+        // Store the file handle
+        masterFileHandle = handleProcessOutput(masterStdout!, processName: "Master")
+        
+        do {
+            logger.info("Starting master process with \(uvPath) run python -m master.main at \(projectPath.path)")
+            try masterProcess?.run()
+            masterStatus = "Running"
+            
+            masterProcess?.terminationHandler = { [weak self] process in
+                DispatchQueue.main.async {
+                    let status = "Stopped (exit: \(process.terminationStatus))"
+                    self?.masterStatus = status
+                    self?.logger.error("Master process terminated: \(status)")
+                    // Cleanup on termination
+                    self?.cleanupProcess(process: self?.masterProcess, 
+                                      fileHandle: self?.masterFileHandle, 
+                                      pipe: self?.masterStdout)
+                }
+            }
+        } catch {
+            masterStatus = "Error: \(error.localizedDescription)"
+            logger.error("Failed to start master: \(error.localizedDescription)")
+            cleanupProcess(process: masterProcess, fileHandle: masterFileHandle, pipe: masterStdout)
+        }
+    }
+    
+    func startWorker() {
+        guard let projectPath = self.projectPath else {
+            workerStatus = "Error: Project directory not found"
+            logger.error("Could not find project directory with pyproject.toml")
+            return
+        }
+        
+        guard let uvPath = self.uvPath else {
+            workerStatus = "Error: uv not found"
+            logger.error("Could not find uv executable in common paths")
+            return
+        }
+        
+        // Cleanup any existing process
+        cleanupProcess(process: workerProcess, fileHandle: workerFileHandle, pipe: workerStdout)
+        
+        workerProcess = Process()
+        workerStdout = Pipe()
+        
+        // Use uv to run the worker module
+        workerProcess?.executableURL = URL(fileURLWithPath: uvPath)
+        workerProcess?.arguments = ["run", "python", "-m", "worker.main"]
+        workerProcess?.standardOutput = workerStdout
+        workerProcess?.standardError = workerStdout
+        
+        // Set up environment
+        var env = ProcessInfo.processInfo.environment
+        env["PYTHONUNBUFFERED"] = "1"
+        env["PYTHONPATH"] = projectPath.path
+        workerProcess?.environment = env
+        
+        // Set working directory to project root
+        workerProcess?.currentDirectoryURL = projectPath
+        
+        // Store the file handle
+        workerFileHandle = handleProcessOutput(workerStdout!, processName: "Worker")
+        
+        do {
+            logger.info("Starting worker process with \(uvPath) run python -m worker.main at \(projectPath.path)")
+            try workerProcess?.run()
+            workerStatus = "Running"
+            
+            workerProcess?.terminationHandler = { [weak self] process in
+                DispatchQueue.main.async {
+                    let status = "Stopped (exit: \(process.terminationStatus))"
+                    self?.workerStatus = status
+                    self?.logger.error("Worker process terminated: \(status)")
+                    // Cleanup on termination
+                    self?.cleanupProcess(process: self?.workerProcess, 
+                                      fileHandle: self?.workerFileHandle, 
+                                      pipe: self?.workerStdout)
+                }
+            }
+        } catch {
+            workerStatus = "Error: \(error.localizedDescription)"
+            logger.error("Failed to start worker: \(error.localizedDescription)")
+            cleanupProcess(process: workerProcess, fileHandle: workerFileHandle, pipe: workerStdout)
+        }
+    }
+    
+    func stopAll() {
+        logger.info("Stopping all processes")
+        
+        // Clean up master process
+        cleanupProcess(process: masterProcess, fileHandle: masterFileHandle, pipe: masterStdout)
+        masterProcess = nil
+        masterStdout = nil
+        masterFileHandle = nil
+        masterStatus = "Stopped"
+        
+        // Clean up worker process
+        cleanupProcess(process: workerProcess, fileHandle: workerFileHandle, pipe: workerStdout)
+        workerProcess = nil
+        workerStdout = nil
+        workerFileHandle = nil
+        workerStatus = "Stopped"
+    }
+    
+    func checkBinaries() -> Bool {
+        guard let projectPath = self.projectPath else {
+            logger.error("Could not find project directory")
+            return false
+        }
+        
+        guard let uvPath = self.uvPath else {
+            logger.error("Could not find uv executable")
+            return false
+        }
+        
+        let fileManager = FileManager.default
+        let pyprojectPath = projectPath.appendingPathComponent("pyproject.toml").path
+        let masterPath = projectPath.appendingPathComponent("master/main.py").path
+        let workerPath = projectPath.appendingPathComponent("worker/main.py").path
+        
+        let uvExists = fileManager.fileExists(atPath: uvPath)
+        let pyprojectExists = fileManager.fileExists(atPath: pyprojectPath)
+        let masterExists = fileManager.fileExists(atPath: masterPath)
+        let workerExists = fileManager.fileExists(atPath: workerPath)
+        
+        if !uvExists {
+            logger.error("uv not found at \(uvPath)")
+        }
+        if !pyprojectExists {
+            logger.error("pyproject.toml not found at \(pyprojectPath)")
+        }
+        if !masterExists {
+            logger.error("master/main.py not found at \(masterPath)")
+        }
+        if !workerExists {
+            logger.error("worker/main.py not found at \(workerPath)")
+        }
+        
+        return uvExists && pyprojectExists && masterExists && workerExists
+    }
+    
+    func toggleLoginItem() {
+        do {
+            if isLoginItemEnabled {
+                try loginService.unregister()
+            } else {
+                try loginService.register()
+            }
+            isLoginItemEnabled = (loginService.status == .enabled)
+        } catch {
+            logger.error("Failed to toggle login item: \(error.localizedDescription)")
+        }
+    }
+    
+    func toggleMasterMode() {
+        isMasterMode.toggle()
+        logger.info("Toggling master mode to: \(self.isMasterMode ? "Master" : "Replica")")
+        
+        // Restart master process with new mode
+        if masterProcess?.isRunning == true {
+            // Clean up current master process
+            cleanupProcess(process: masterProcess, fileHandle: masterFileHandle, pipe: masterStdout)
+            masterProcess = nil
+            masterStdout = nil
+            masterFileHandle = nil
+            masterStatus = "Stopped"
+            
+            // Start master with new mode after a brief delay
+            DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
+                self.startMaster()
+            }
+        }
+    }
+} 
\ No newline at end of file
diff --git a/app/exov2/exov2/exov2.entitlements b/app/exov2/exov2/exov2.entitlements
new file mode 100644
index 00000000..9b5d06d4
--- /dev/null
+++ b/app/exov2/exov2/exov2.entitlements
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+    <key>com.apple.security.app-sandbox</key>
+    <false/>
+    <key>com.apple.security.cs.allow-unsigned-executable-memory</key>
+    <true/>
+    <key>com.apple.security.cs.disable-library-validation</key>
+    <true/>
+    <key>com.apple.security.automation.apple-events</key>
+    <true/>
+</dict>
+</plist>
diff --git a/app/exov2/exov2/exov2App.swift b/app/exov2/exov2/exov2App.swift
new file mode 100644
index 00000000..2a6910b6
--- /dev/null
+++ b/app/exov2/exov2/exov2App.swift
@@ -0,0 +1,115 @@
+//
+//  exov2App.swift
+//  exov2
+//
+//  Created by Sami Khan on 2025-07-27.
+//
+
+import SwiftUI
+import AppKit
+import Foundation
+import OSLog
+import ServiceManagement
+
+@main
+struct exov2App: App {
+    @StateObject private var processManager = ProcessManager()
+    
+    private func resizedMenuBarIcon(named: String, size: CGFloat = 18.0) -> NSImage? {
+        guard let original = NSImage(named: named) else {
+            print("Failed to load image named: \(named)")
+            return nil
+        }
+        
+        let resized = NSImage(size: NSSize(width: size, height: size), flipped: false) { rect in
+            NSGraphicsContext.current?.imageInterpolation = .high
+            original.draw(in: rect)
+            return true
+        }
+        
+        resized.isTemplate = false
+        resized.size = NSSize(width: size, height: size)
+        return resized
+    }
+    
+    var body: some Scene {
+        MenuBarExtra {
+            MenuBarView(processManager: processManager)
+        } label: {
+            if let resizedImage = resizedMenuBarIcon(named: "menubar-icon") {
+                Image(nsImage: resizedImage)
+                    .opacity(processManager.masterStatus == "Running" ? 1.0 : 0.5)
+            }
+        }
+        .menuBarExtraStyle(.window)
+    }
+}
+
+struct MenuBarView: View {
+    @ObservedObject var processManager: ProcessManager
+    
+    var body: some View {
+        VStack(alignment: .leading, spacing: 8) {
+            StatusSection(processManager: processManager)
+            
+            Divider()
+            
+            Toggle("Launch at Login", isOn: Binding(
+                get: { processManager.isLoginItemEnabled },
+                set: { _ in processManager.toggleLoginItem() }
+            ))
+            .padding(.horizontal)
+            
+            Toggle("Is Master?", isOn: Binding(
+                get: { processManager.isMasterMode },
+                set: { _ in processManager.toggleMasterMode() }
+            ))
+            .padding(.horizontal)
+            
+            Divider()
+            
+            Button("Quit") {
+                NSApplication.shared.terminate(nil)
+            }
+        }
+        .padding()
+        .frame(width: 250)
+        .onAppear {
+            if !processManager.checkBinaries() {
+                showEnvironmentError()
+            }
+        }
+    }
+    
+    private func showEnvironmentError() {
+        let alert = NSAlert()
+        alert.messageText = "Python Environment Error"
+        alert.informativeText = "Could not find the required Python environment, uv, or project files. Please ensure uv is installed and the project directory is accessible."
+        alert.alertStyle = .critical
+        alert.addButton(withTitle: "OK")
+        alert.runModal()
+        NSApplication.shared.terminate(nil)
+    }
+}
+
+struct StatusSection: View {
+    @ObservedObject var processManager: ProcessManager
+    
+    var body: some View {
+        VStack(alignment: .leading, spacing: 4) {
+            HStack {
+                Text("Master:")
+                    .bold()
+                Text(processManager.masterStatus)
+                    .foregroundColor(processManager.masterStatus == "Running" ? .green : .red)
+            }
+            
+            HStack {
+                Text("Worker:")
+                    .bold()
+                Text(processManager.workerStatus)
+                    .foregroundColor(processManager.workerStatus == "Running" ? .green : .red)
+            }
+        }
+    }
+}
diff --git a/app/exov2/exov2Tests/exov2Tests.swift b/app/exov2/exov2Tests/exov2Tests.swift
new file mode 100644
index 00000000..dd137fbd
--- /dev/null
+++ b/app/exov2/exov2Tests/exov2Tests.swift
@@ -0,0 +1,17 @@
+//
+//  exov2Tests.swift
+//  exov2Tests
+//
+//  Created by Sami Khan on 2025-07-27.
+//
+
+import Testing
+@testable import exov2
+
+struct exov2Tests {
+
+    @Test func example() async throws {
+        // Write your test here and use APIs like `#expect(...)` to check expected conditions.
+    }
+
+}
diff --git a/app/exov2/exov2UITests/exov2UITests.swift b/app/exov2/exov2UITests/exov2UITests.swift
new file mode 100644
index 00000000..db1586a9
--- /dev/null
+++ b/app/exov2/exov2UITests/exov2UITests.swift
@@ -0,0 +1,43 @@
+//
+//  exov2UITests.swift
+//  exov2UITests
+//
+//  Created by Sami Khan on 2025-07-27.
+//
+
+import XCTest
+
+final class exov2UITests: XCTestCase {
+
+    override func setUpWithError() throws {
+        // Put setup code here. This method is called before the invocation of each test method in the class.
+
+        // In UI tests it is usually best to stop immediately when a failure occurs.
+        continueAfterFailure = false
+
+        // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
+    }
+
+    override func tearDownWithError() throws {
+        // Put teardown code here. This method is called after the invocation of each test method in the class.
+    }
+
+    @MainActor
+    func testExample() throws {
+        // UI tests must launch the application that they test.
+        let app = XCUIApplication()
+        app.launch()
+
+        // Use XCTAssert and related functions to verify your tests produce the correct results.
+    }
+
+    @MainActor
+    func testLaunchPerformance() throws {
+        if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) {
+            // This measures how long it takes to launch your application.
+            measure(metrics: [XCTApplicationLaunchMetric()]) {
+                XCUIApplication().launch()
+            }
+        }
+    }
+}
diff --git a/app/exov2/exov2UITests/exov2UITestsLaunchTests.swift b/app/exov2/exov2UITests/exov2UITestsLaunchTests.swift
new file mode 100644
index 00000000..928b4443
--- /dev/null
+++ b/app/exov2/exov2UITests/exov2UITestsLaunchTests.swift
@@ -0,0 +1,33 @@
+//
+//  exov2UITestsLaunchTests.swift
+//  exov2UITests
+//
+//  Created by Sami Khan on 2025-07-27.
+//
+
+import XCTest
+
+final class exov2UITestsLaunchTests: XCTestCase {
+
+    override class var runsForEachTargetApplicationUIConfiguration: Bool {
+        true
+    }
+
+    override func setUpWithError() throws {
+        continueAfterFailure = false
+    }
+
+    @MainActor
+    func testLaunch() throws {
+        let app = XCUIApplication()
+        app.launch()
+
+        // Insert steps here to perform after app launch but before taking a screenshot,
+        // such as logging into a test account or navigating somewhere in the app
+
+        let attachment = XCTAttachment(screenshot: app.screenshot())
+        attachment.name = "Launch Screen"
+        attachment.lifetime = .keepAlways
+        add(attachment)
+    }
+}

← 71bafabc Dashboard with instances  ·  back to Exo  ·  Remove rust 92c9688b →