Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
version: 2
updates:
# Maintain GitHub Actions dependencies
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"

# Maintain Gradle dependencies and plugins
- package-ecosystem: "gradle"
directory: "/"
schedule:
interval: "weekly"
27 changes: 24 additions & 3 deletions .github/workflows/build-desktop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ jobs:
artifact-path: 'app-desktop/build/compose/binaries/main*/**/*.deb'
platform: linux

env:
JAVA_VERSION: ${{ vars.JAVA_VERSION || '21' }}

defaults:
run:
shell: bash
Expand All @@ -46,11 +49,11 @@ jobs:
- name: Checkout Code
uses: actions/checkout@v6

- name: Setup JDK 17
- name: Setup JDK
uses: actions/setup-java@v5
with:
distribution: 'zulu'
java-version: '17'
java-version: ${{ env.JAVA_VERSION }}

- name: Setup Gradle
uses: gradle/actions/setup-gradle@v6
Expand Down Expand Up @@ -213,6 +216,24 @@ jobs:
echo "Production-level version detected ($APP_VERSION). No filename mapping required."
fi

- name: Rename pre-release package files superficially
run: |
if [[ "$APP_VERSION" =~ ^0\. ]]; then
# Under the hood, Gradle maps 0.x.y to 1.x.y to satisfy macOS CFBundleVersion rules.
# Here we rename the generated file names back to 0.x.y so the user sees a pre-release version name.
MAPPED_VERSION="1.${APP_VERSION#0.}"
echo "Pre-release version detected ($APP_VERSION). Searching for generated files matching mapped version $MAPPED_VERSION..."

# Find files containing the mapped version and rename them to use the actual version
find app-desktop/build/compose/binaries/main* -type f \( -name "*${MAPPED_VERSION}*" \) | while read -r file; do
new_file=$(echo "$file" | sed "s/${MAPPED_VERSION}/${APP_VERSION}/g")
echo "Renaming $file to $new_file"
mv "$file" "$new_file"
done
else
echo "Production-level version detected ($APP_VERSION). No filename mapping required."
fi

- name: Upload Main Package Artifact
uses: actions/upload-artifact@v7
with:
Expand All @@ -221,7 +242,7 @@ jobs:
retention-days: 7

- name: Upload PKG Package Artifact (macOS only)
if: matrix.platform == 'macos'
if: matrix.platform == 'macos' && env.MACOS_INSTALLER_PRESENT == 'true'
uses: actions/upload-artifact@v7
with:
name: Codeoba-macos-pkg
Expand Down
13 changes: 11 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ When modifying the Compose UI under `app-desktop`, adhere to these style guideli
- Free local lexical and semantic searches are enabled by default for all users, while AI-powered summarization is a premium subscription feature.
- Paid subscription entitlements are enforced strictly on the backend to gate access to the Device Sync Hub and remote command relay APIs.
- Implements secure, browser-delegated OAuth flow utilizing a temporary JDK-native HTTP loopback server (listening on a random port for `/callback` parameters) and a unified Web Console SPA (running on Firebase Hosting) to prevent in-app credential handling.
- Stores all sensitive authentication credentials (ID and refresh tokens, licensing JWT, decryption key) in the OS-native keyring (Keychain on macOS, Credential Manager on Windows, Secret Service on Linux) using a secure utility with automatic self-healing migration and a Java Preferences fallback. Keychain/Keyring prompts can be bypassed completely during development or troubleshooting by setting the JVM system property `codeoba.no.keyring=true`.
- Stores all sensitive authentication credentials (ID and refresh tokens, licensing JWT, decryption key) in the OS-native keyring (Keychain on macOS, Credential Manager on Windows, Secret Service on Linux) using a secure utility with automatic self-healing migration and a Java Preferences fallback. Keychain/Keyring prompts are bypassed by default in non-production configurations to simplify testing, and can be explicitly controlled via the JVM system property `codeoba.no.keyring=<true|false>`.
- Billing webhooks query subscriptions using the user's immutable Firebase `uid` mapped in checkout custom metadata rather than mutable emails to support profile email updates.
- Implements challenge-response authentication utilizing 90-second single-use nonces and device public/private key pairs (stored securely in the OS-native keyring via `SecureStorage` with a fallback to Preferences).
- Integrates a best-effort local regex secrets scanner to redact sensitive credentials on the client side before synchronizing data.
Expand All @@ -200,10 +200,19 @@ When modifying the Compose UI under `app-desktop`, adhere to these style guideli
- The Firebase Web API key used for token refresh operations is resolved dynamically via the JVM system property `codeoba.firebase.api_key` or environment variable `CODEOBA_FIREBASE_API_KEY`.
- If running in production (non-emulator) mode and the API key matches the default placeholder or is blank, a fail-fast validation check throws an `IllegalArgumentException` with clear configuration instructions.

27. **App Signature Configuration**:
- The client app injects an app signature token resolved at build-time (from environment `CODEOBA_APP_SIGNATURE_HASH` or build properties) into `BuildConfig.APP_SIGNATURE`.
- This token is sent as the `X-App-Signature` header in all requests to backend functions. The backend compares it to `CODEOBA_APP_SIGNATURE_HASH` to verify that the request originates from the official signed distribution.

28. **Store Screenshot Generator & Mock Mode**:
- Restricted to debug builds only (`BuildConfig.DEBUG == true`). Activated using the JVM system property `-Dcodeoba.store=apple|microsoft` (with optional custom JSON path via `-Dcodeoba.canned_data=PATH`).
- Sized and centered to specific default dimensions (Apple: 1280x800, Microsoft: 1920x1080) unless overridden via the program argument `--size=WIDTHxHEIGHT`.
- Intercepts directory/database scanning and loads faked, high-quality session lists from `store/canned_apple.json` or `store/canned_microsoft.json` relative to the current working directory, preventing local data updates to settings.

---

## 🛠️ Common Gradle Development Commands

- Compile Desktop Client: `./gradlew :app-desktop:compileKotlinDesktop`
- Run Unit Tests: `./gradlew :core:desktopTest`
- Launch Application in Dev Mode: `./gradlew :app-desktop:run`
- Launch Application in Dev Mode: `./gradlew :app-desktop:run -Dcodeoba.base_url=localhost:5000`
80 changes: 75 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,15 +60,85 @@ graph TD
- **Test**: `./gradlew :core:desktopTest`
- **Launch Application**: `./gradlew :app-desktop:run`

### Environment & Configuration Properties

The application resolves dynamic parameters at startup from JVM system properties or environment variables:
- `codeoba.firebase.api_key` (JVM property) or `CODEOBA_FIREBASE_API_KEY` (env variable): Firebase Web API Key required for token refresh operations in non-emulator (production) mode. If missing in production, token refreshes fail fast.
- `codeoba.base_url` (JVM property): The active environment base URL. Defaults to `codeoba.com`. If configured to `localhost` or `127.0.0.1`, the app targets local Firebase emulators.
### ⚙️ Developer Runtime Modes & Configuration

By default, the application runs in **Free/Local-First Mode** (zero cloud dependencies, subscription features compiled out). For developers working on subscription, syncing, or remote-control features, the app supports three environments:

#### 1. Free/Local-First Mode (Default)
Runs 100% locally on your machine with no external credentials, internet, or databases required.
* **Compile-Time Setup**: No properties file or key configuration is needed.
* **Run**:
```bash
./gradlew :app-desktop:run
```

#### 2. Local Emulator Development Mode
Enables paid subscription and multi-device sync UI features locally by connecting to a local offline Firebase Emulator Suite.
* **Compile-Time Setup**:
Create `local.properties` in the root folder of this repository (it is git-ignored by default) and add:
```properties
# Enable subscription and sync features in the UI
# (Note: This is a temporary developer toggle to A/B test the client with/without subscription
# features. Upon subscription release, this option will be removed and permanently enabled).
codeoba.enable_subscription=true

# The native OS keyring is automatically bypassed by default in local/staging environments
# to prevent repeated keychain authorization prompts when unsigned developer builds are run.
```
*(Note: When pointing to the local emulator, the client defaults to `EMULATOR_ONLY` for the Firebase API key. However, because subscription features are enabled, you must configure a public verification key (`codeoba.premium.public_key=...`) in `local.properties` to verify the premium module signature. Developers with access to the premium module can generate and pair these key properties automatically using the developer key setup tasks).*
* **Run**: Start the app by passing the local emulator base URL as a JVM System Property:
```bash
./gradlew :app-desktop:run -Dcodeoba.base_url=localhost:5000
```

#### 3. Deployed Dev Server Mode (Staging sandbox pointing to dev.codeoba.com)
Connects the local client app directly to the public staging cloud backend (Firebase project `codeoba-dev`).
* **Compile-Time Setup**:
Add the following compile-time variables to `local.properties` (or set them as environment variables in your build shell):
```properties
codeoba.enable_subscription=true

# Paste your staging Firebase Web API Key:
codeoba.firebase.api_key=YOUR_STAGING_FIREBASE_WEB_API_KEY

# Paste your staging Public Key (pairs with premium private key):
codeoba.premium.public_key=YOUR_STAGING_PREMIUM_PUBLIC_KEY

# Paste your staging app signature attestation token:
codeoba.app_signature_hash=YOUR_STAGING_APP_SIGNATURE_TOKEN
```
*(If using environment variables, export them in your terminal before building: `CODEOBA_FIREBASE_API_KEY`, `CODEOBA_PREMIUM_PUBLIC_KEY`, and `CODEOBA_APP_SIGNATURE_HASH`).*
* **Run**: Run the client pointing to the staging base URL:
```bash
./gradlew :app-desktop:run -Dcodeoba.base_url=dev.codeoba.com
```

#### 4. Deployed Production Mode (Pointing to codeoba.com)
Connects the client app to the live production database and backend services.
* **Compile-Time Setup**:
Add the production variables to `local.properties` (or export as environment variables):
```properties
codeoba.enable_subscription=true
codeoba.firebase.api_key=YOUR_PRODUCTION_FIREBASE_WEB_API_KEY
codeoba.premium.public_key=YOUR_PRODUCTION_PREMIUM_PUBLIC_KEY
codeoba.app_signature_hash=YOUR_PRODUCTION_APP_SIGNATURE_TOKEN
```
* **Run**: Run the client pointing to the production base URL:
```bash
./gradlew :app-desktop:run -Dcodeoba.base_url=codeoba.com
```

---

## 🔒 Privacy & Local-First Philosophy

* 100% local-first: no remote accounts, telemetry, trackers, or cloud storage syncing.
* All parser steps, SQL queries, and semantic embeddings are executed directly on your local machine.

---

## 📸 Developer Guides

* [Adding a New Log Adapter Guide](file:///Users/pv/Dev/GitHub/LookAtWhatAiCanDo/Codeoba/docs/ADD_NEW_SOURCE.md)
* [Developer Setup & Build Guide](file:///Users/pv/Dev/GitHub/LookAtWhatAiCanDo/Codeoba/docs/DEVELOPMENT.md)
* [Marketing Screenshot Mock Generator Guide](file:///Users/pv/Dev/GitHub/LookAtWhatAiCanDo/Codeoba/docs/SCREENSHOT_GENERATOR.md)
15 changes: 15 additions & 0 deletions app-desktop/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ kotlin {
implementation(libs.compose.material3)
implementation(libs.compose.runtime)
implementation(libs.kotlinx.serialization.json)
implementation(libs.kotlinx.cli)
implementation(libs.compose.ui)
implementation(libs.slf4j.simple)
}
Expand Down Expand Up @@ -72,6 +73,20 @@ compose {
includeAllModules = true
macOS {
iconFile.set(project.file("src/desktopMain/resources/icon.icns"))
bundleID = "com.whataicando.codeoba"
val signingIdentity = System.getenv("MACOS_SIGNING_IDENTITY")
if (!signingIdentity.isNullOrBlank()) {
signing {
sign.set(true)
identity.set(signingIdentity)
entitlementsFile.set(project.file("src/desktopMain/resources/entitlements.plist"))
}
notarization {
appleID.set(System.getenv("APPLE_ID"))
password.set(System.getenv("APPLE_ID_PASSWORD"))
teamID.set(System.getenv("APPLE_TEAM_ID"))
}
}
}
windows {
iconFile.set(project.file("src/desktopMain/resources/icon.ico"))
Expand Down
Loading
Loading