| .gitignore | patch | view | blame | history | |
| android/app/build.gradle.kts | patch | view | blame | history | |
| tools/build-android-aab.sh | patch | view | blame | history | |
| tools/build-appstore.sh | patch | view | blame | history | |
| tools/privacy.html | patch | view | blame | history | |
| tools/release.sh | patch | view | blame | history |
.gitignore
.. .. @@ -43,3 +43,7 @@ 43 43 /android/app/debug 44 44 /android/app/profile 45 45 /android/app/release 46 +47 +# Release signing — never commit the keystore.48 +android/release.keystore49 +android/key.propertiesandroid/app/build.gradle.kts
.. .. @@ -20,21 +20,30 @@ 20 20 } 21 21 22 22 defaultConfig { 23 - // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).24 23 applicationId = "com.tekmidian.pailot" 25 - // You can update the following values to match your application needs.26 - // For more information, see: https://flutter.dev/to/review-gradle-config.27 24 minSdk = flutter.minSdkVersion 28 25 targetSdk = flutter.targetSdkVersion 29 26 versionCode = flutter.versionCode 30 27 versionName = flutter.versionName 31 28 } 32 29 30 + // Release signing — keystore lives at android/release.keystore (gitignored).31 + // Back this file up: losing it means losing the ability to ship updates to32 + // the Play Store under this package name.33 + signingConfigs {34 + create("release") {35 + storeFile = file("../release.keystore")36 + storePassword = "pailot-release-key-2026"37 + keyAlias = "pailot"38 + keyPassword = "pailot-release-key-2026"39 + }40 + }41 +33 42 buildTypes { 34 43 release { 35 - // TODO: Add your own signing config for the release build.36 - // Signing with the debug keys for now, so `flutter run --release` works.37 - signingConfig = signingConfigs.getByName("debug")44 + signingConfig = signingConfigs.getByName("release")45 + isMinifyEnabled = true46 + proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"))38 47 } 39 48 } 40 49 } tools/build-android-aab.sh
.. .. @@ -0,0 +1,22 @@ 1 +#!/bin/bash2 +# PAILot Android AAB Build Script3 +# Produces a Play Store-ready signed App Bundle.4 +set -e5 +6 +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"7 +APP_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"8 +cd "$APP_DIR"9 +10 +# Pin JDK 21 — system openjdk 25 is not yet parseable by the Kotlin compiler11 +# bundled with AGP. JDK 17 also works if you prefer; just point JAVA_HOME at it.12 +JDK="${JAVA_HOME_RELEASE:-/opt/homebrew/Cellar/openjdk@21/21.0.11/libexec/openjdk.jdk/Contents/Home}"13 +14 +echo "=== Building Android App Bundle (AAB) ==="15 +JAVA_HOME="$JDK" flutter build appbundle --release --no-tree-shake-icons16 +17 +echo ""18 +echo "=== Done ==="19 +AAB=build/app/outputs/bundle/release/app-release.aab20 +ls -la "$AAB"21 +echo ""22 +echo "Upload $AAB to the Play Console (Production → Create new release)."tools/build-appstore.sh
.. .. @@ -0,0 +1,82 @@ 1 +#!/bin/bash2 +# PAILot App Store Build Script3 +# Builds an iOS IPA and uploads it to App Store Connect.4 +# Adapted from Glidr's pipeline — same Apple Dev Team (7KU642K5ZL).5 +set -e6 +7 +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"8 +APP_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"9 +cd "$APP_DIR"10 +11 +# App Store Connect API credentials (issuer is shared across keys for this team).12 +ASC_KEY_ID="${ASC_KEY_ID:-4H68LK2M3J}"13 +ASC_ISSUER_ID="${ASC_ISSUER_ID:-69a6de75-2050-47e3-e053-5b8c7c11a4d1}"14 +ASC_KEY_PATH="${ASC_KEY_PATH:-$HOME/.appstoreconnect/private_keys/AuthKey_${ASC_KEY_ID}.p8}"15 +TEAM_ID="7KU642K5ZL"16 +17 +if [ ! -f "$ASC_KEY_PATH" ]; then18 + echo "ERROR: App Store Connect API key not found at $ASC_KEY_PATH"19 + echo " Download from https://appstoreconnect.apple.com/access/api"20 + exit 121 +fi22 +23 +# Homebrew rsync breaks Xcode's export step — temporarily hide it.24 +BREW_RSYNC="$(brew --prefix 2>/dev/null)/bin/rsync"25 +RSYNC_MOVED=false26 +if [ -f "$BREW_RSYNC" ]; then27 + mv "$BREW_RSYNC" "${BREW_RSYNC}.bak"28 + RSYNC_MOVED=true29 + echo " Temporarily hid Homebrew rsync"30 +fi31 +cleanup() {32 + if [ "$RSYNC_MOVED" = true ] && [ -f "${BREW_RSYNC}.bak" ]; then33 + mv "${BREW_RSYNC}.bak" "$BREW_RSYNC"34 + echo " Restored Homebrew rsync"35 + fi36 +}37 +trap cleanup EXIT38 +39 +echo "=== Building IPA for App Store ==="40 +flutter build ipa --release --export-method development --no-tree-shake-icons41 +42 +echo ""43 +echo "=== Fixing archive name ==="44 +ARCHIVE="build/ios/archive/Runner.xcarchive/Info.plist"45 +if [ -f "$ARCHIVE" ]; then46 + plutil -replace Name -string "PAILot" "$ARCHIVE"47 + plutil -replace SchemeName -string "PAILot" "$ARCHIVE"48 + echo " Archive name set to 'PAILot'"49 +fi50 +51 +echo ""52 +echo "=== Exporting and uploading to App Store Connect ==="53 +cat > /tmp/PAILotAppStoreExport.plist <<PLIST54 +<?xml version="1.0" encoding="UTF-8"?>55 +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">56 +<plist version="1.0">57 +<dict>58 + <key>method</key>59 + <string>app-store-connect</string>60 + <key>destination</key>61 + <string>upload</string>62 + <key>signingStyle</key>63 + <string>automatic</string>64 + <key>teamID</key>65 + <string>${TEAM_ID}</string>66 +</dict>67 +</plist>68 +PLIST69 +70 +rm -rf /tmp/pailot-appstore-export 2>/dev/null71 +xcodebuild -exportArchive \72 + -archivePath build/ios/archive/Runner.xcarchive \73 + -exportOptionsPlist /tmp/PAILotAppStoreExport.plist \74 + -exportPath /tmp/pailot-appstore-export \75 + -allowProvisioningUpdates \76 + -authenticationKeyPath "$ASC_KEY_PATH" \77 + -authenticationKeyID "$ASC_KEY_ID" \78 + -authenticationKeyIssuerID "$ASC_ISSUER_ID"79 +80 +echo ""81 +echo "=== Done ==="82 +echo "Uploaded to App Store Connect. Next: complete the build's metadata and submit for review."tools/privacy.html
.. .. @@ -0,0 +1,95 @@ 1 +<!DOCTYPE html>2 +<html lang="en">3 +<head>4 + <meta charset="UTF-8">5 + <meta name="viewport" content="width=device-width, initial-scale=1.0">6 + <meta name="description" content="Privacy Policy for the PAILot voice-first AI communicator app">7 + <title>Privacy Policy - PAILot</title>8 + <style>9 + * { margin: 0; padding: 0; box-sizing: border-box; }10 + body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; line-height: 1.6; color: #333; background-color: #f9f9f9; }11 + .container { max-width: 900px; margin: 0 auto; padding: 20px; background-color: #ffffff; }12 + header { border-bottom: 2px solid #007AFF; padding-bottom: 20px; margin-bottom: 30px; }13 + h1 { font-size: 2.5rem; color: #007AFF; margin-bottom: 10px; }14 + .subtitle { color: #666; font-size: 1rem; }15 + .last-updated { color: #999; font-size: 0.9rem; margin-top: 10px; }16 + h2 { font-size: 1.5rem; color: #007AFF; margin-top: 35px; margin-bottom: 15px; }17 + h3 { font-size: 1.1rem; color: #333; margin-top: 20px; margin-bottom: 10px; }18 + p { margin-bottom: 15px; text-align: justify; }19 + ul { margin-left: 30px; margin-bottom: 15px; }20 + li { margin-bottom: 8px; }21 + .highlight { background-color: #f0f8ff; border-left: 4px solid #007AFF; padding: 15px; margin: 20px 0; }22 + footer { margin-top: 40px; padding-top: 20px; border-top: 1px solid #eee; color: #666; font-size: 0.9rem; text-align: center; }23 + @media (max-width: 600px) { .container { padding: 15px; } h1 { font-size: 2rem; } h2 { font-size: 1.3rem; } }24 + </style>25 +</head>26 +<body>27 + <div class="container">28 + <header>29 + <h1>Privacy Policy</h1>30 + <p class="subtitle">PAILot — voice-first AI communicator</p>31 + <p class="last-updated">Last updated: June 2026</p>32 + </header>33 +34 + <h2>Introduction</h2>35 + <p>PAILot is a client application that connects to a personal AI infrastructure server (AIBroker) that you operate. PAILot is intentionally designed so that we, TEKmidian, do not host, store, or process your data on our infrastructure. The app's purpose is to relay your voice and text messages between you and the Claude Code sessions running on your own machine.</p>36 +37 + <div class="highlight">38 + <strong>Plain English:</strong> PAILot talks to a server you run yourself. We don't see your messages, your voice, or your conversations. There is no PAILot cloud.39 + </div>40 +41 + <h2>What PAILot Does Not Do</h2>42 + <ul>43 + <li>We do not operate a backend. The app connects only to the AIBroker daemon you have configured.</li>44 + <li>We do not collect or store any of your messages, voice recordings, transcripts, or session data.</li>45 + <li>We do not use third-party analytics SDKs (no Firebase Analytics, no Crashlytics by default, no advertising IDs).</li>46 + <li>We do not track you across apps or websites.</li>47 + <li>We do not sell or share any data with third parties.</li>48 + </ul>49 +50 + <h2>What the App Processes Locally and Sends to Your Server</h2>51 +52 + <h3>Voice recordings</h3>53 + <p>When you record a voice message, the audio is captured locally on your device and sent over an encrypted MQTT connection to <em>your own</em> AIBroker daemon. Transcription happens on that machine. Neither the audio nor the transcript leaves your infrastructure.</p>54 +55 + <h3>Text messages and screenshots</h3>56 + <p>Text messages and screenshots you send are transmitted directly to your AIBroker daemon. They are not routed through any TEKmidian server.</p>57 +58 + <h3>Push notification tokens</h3>59 + <p>To deliver notifications when the app is in the background, your device's APNs token (iOS) or FCM token (Android) is sent to your AIBroker daemon, which uses it to push notifications via Apple or Google. The token never reaches TEKmidian.</p>60 +61 + <h3>Local storage on your device</h3>62 + <p>The app stores conversation history, session names, unread counts, and configuration locally on the device using the operating system's standard private app sandbox. This data is removed when you uninstall the app.</p>63 +64 + <h2>Permissions the App Requests</h2>65 + <ul>66 + <li><strong>Microphone</strong> — required to record voice messages. Used only when you actively initiate a recording.</li>67 + <li><strong>Notifications</strong> — required to alert you to incoming messages.</li>68 + <li><strong>Camera (optional)</strong> — used only when you explicitly attach a photo.</li>69 + <li><strong>Network</strong> — required to communicate with your AIBroker server.</li>70 + </ul>71 +72 + <h2>Third-Party Services Embedded in the App</h2>73 + <p>The app relies on the operating system's push notification systems (Apple Push Notification service and Firebase Cloud Messaging) <em>solely</em> as a delivery channel. These platforms have their own privacy policies governing notification delivery, which apply when your AIBroker server sends a push through them.</p>74 +75 + <h2>Data Retention</h2>76 + <p>We retain none of your data because we never receive any. Anything stored on your device is governed by your device's privacy and backup settings. Data on your AIBroker server is governed by your own decisions about how you run that server.</p>77 +78 + <h2>Children's Privacy</h2>79 + <p>PAILot is not directed at children under 13. We do not knowingly collect information from children.</p>80 +81 + <h2>Changes to This Privacy Policy</h2>82 + <p>If this policy materially changes, the updated version will be available at the same URL and announced in the app's release notes. The "Last updated" date above will reflect the most recent change.</p>83 +84 + <h2>Your Rights</h2>85 + <p>Because we do not process your personal data, GDPR/CCPA "access, correction, deletion" requests do not produce anything on our side. To remove all locally stored data, uninstall the app. To remove data held on your AIBroker server, manage it directly on the machine where that server runs.</p>86 +87 + <h2>Contact Us</h2>88 + <p>If you have questions about this policy, contact us at <a href="mailto:support@tekmidian.com">support@tekmidian.com</a>.</p>89 +90 + <footer>91 + <p>© 2026 TEKmidian. All rights reserved.</p>92 + </footer>93 + </div>94 +</body>95 +</html>tools/release.sh
.. .. @@ -0,0 +1,59 @@ 1 +#!/bin/bash2 +# PAILot full-store release pipeline.3 +# Builds iOS IPA, uploads to App Store Connect, then builds Android AAB.4 +#5 +# Prereqs:6 +# - Apple Dev Team 7KU642K5ZL provisioning set up (same as Glidr).7 +# - App Store Connect API key at $HOME/.appstoreconnect/private_keys/AuthKey_*.p88 +# - android/release.keystore present (generated once; back this up!).9 +#10 +# Usage:11 +# ./tools/release.sh # iOS + Android12 +# ./tools/release.sh --ios-only # skip Android13 +# ./tools/release.sh --android-only # skip iOS14 +set -e15 +16 +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"17 +APP_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"18 +cd "$APP_DIR"19 +20 +IOS=true21 +ANDROID=true22 +for arg in "$@"; do23 + case "$arg" in24 + --ios-only) ANDROID=false ;;25 + --android-only) IOS=false ;;26 + esac27 +done28 +29 +# Kill any competing flutter/dart processes (other sessions hold locks).30 +pkill -f "dartvm.*flutter_tools" 2>/dev/null || true31 +sleep 132 +33 +APP_VERSION=$(grep "^version:" pubspec.yaml | sed 's/version: //')34 +echo "========================================="35 +echo " PAILot store release v${APP_VERSION}"36 +echo "========================================="37 +echo ""38 +39 +if $IOS; then40 + echo "[iOS] Building IPA and uploading to App Store Connect..."41 + bash tools/build-appstore.sh42 + echo ""43 +fi44 +45 +if $ANDROID; then46 + echo "[Android] Building AAB..."47 + bash tools/build-android-aab.sh48 + echo ""49 +fi50 +51 +echo "========================================="52 +echo " RELEASE COMPLETE"53 +echo "========================================="54 +echo ""55 +$IOS && echo "iOS: Uploaded to App Store Connect."56 +$ANDROID && echo "Android: build/app/outputs/bundle/release/app-release.aab"57 +$ANDROID && echo " Upload to Google Play Console manually."58 +echo ""59 +echo "Next: bump the build number on each store and submit for review."