From b4dce66cbe90ac1d314afe7361e637097eef5b47 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 5 May 2026 06:31:18 +0000 Subject: [PATCH 01/12] Harden APK flow and Android landing distribution Co-authored-by: Andrew | Godmode --- .github/workflows/build-apk.yml | 10 +- DEPLOY_APK.md | 58 +++++++++++ assets/adaptive-icon.png | Bin 0 -> 68 bytes assets/favicon.png | Bin 0 -> 68 bytes assets/icon.png | Bin 0 -> 68 bytes assets/splash.png | Bin 0 -> 68 bytes eas.json | 6 +- package.json | 8 +- public/index.html | 159 ++++++++++++++++++++++++++--- src/ai/NarrativeService.js | 89 +++++++++++++--- src/core/SceneEngine.js | 5 +- src/core/services/GitHubService.js | 50 +++++++-- 12 files changed, 342 insertions(+), 43 deletions(-) create mode 100644 DEPLOY_APK.md create mode 100644 assets/adaptive-icon.png create mode 100644 assets/favicon.png create mode 100644 assets/icon.png create mode 100644 assets/splash.png diff --git a/.github/workflows/build-apk.yml b/.github/workflows/build-apk.yml index 097c8d6..29dafab 100644 --- a/.github/workflows/build-apk.yml +++ b/.github/workflows/build-apk.yml @@ -2,6 +2,8 @@ name: Build Android APK on: push: branches: [ main ] + pull_request: + branches: [ main ] workflow_dispatch: jobs: @@ -16,7 +18,7 @@ jobs: with: node-version: 20 cache: 'npm' - cache-dependency-path: App_369/package-lock.json + cache-dependency-path: package-lock.json - name: ๐Ÿ— Setup Expo uses: expo/expo-github-action@v8 @@ -26,9 +28,7 @@ jobs: token: ${{ secrets.EXPO_TOKEN }} - name: ๐Ÿ“ฆ Install dependencies - working-directory: ./App_369 - run: npm install + run: npm ci - name: ๐Ÿš€ Build APK - working-directory: ./App_369 - run: eas build --platform android --profile preview --non-interactive + run: npm run build:apk diff --git a/DEPLOY_APK.md b/DEPLOY_APK.md new file mode 100644 index 0000000..24e07c3 --- /dev/null +++ b/DEPLOY_APK.md @@ -0,0 +1,58 @@ +# APK Distribution Runbook + +This runbook covers generating and publishing the Android APK so users can install ASTRA MATRIX from your landing page. + +## 1) Build the APK with EAS + +Use the preview profile for downloadable APK output: + +```bash +npm ci +npm run build:apk +``` + +Use production profile when you are ready for release-grade signing controls: + +```bash +npm run build:apk:production +``` + +## 2) Retrieve the build artifact + +After `eas build` completes, download the generated APK and rename it: + +```bash +astra-matrix-latest.apk +``` + +## 3) Publish on your server + +Upload the APK to your web root so it is publicly available at: + +```text +/download/astra-matrix-latest.apk +``` + +Your landing page already links to this path. + +## 4) Recommended web server headers for APK + +Set these response headers for the APK file: + +- `Content-Type: application/vnd.android.package-archive` +- `Content-Disposition: attachment; filename="astra-matrix-latest.apk"` + +## 5) Post-release smoke test + +From an Android device: + +1. Open landing page +2. Tap **Download APK** +3. Install package +4. Launch app and verify startup scene renders without crash + +## 6) Security and signing reminders + +- Keep EAS signing credentials backed up and access-controlled. +- Never embed private keys or long-lived API secrets directly in the mobile client. +- If Gemini integration is required in production, use a backend proxy/API and not a raw client-side secret. diff --git a/assets/adaptive-icon.png b/assets/adaptive-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..963dcd613d9ef8c44119ac18467b7783e5d13dfd GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcwN$fBwreFf%hTyt{cz Q3nFVdQ&MBb@0E)E^i~s-t literal 0 HcmV?d00001 diff --git a/assets/favicon.png b/assets/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..963dcd613d9ef8c44119ac18467b7783e5d13dfd GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcwN$fBwreFf%hTyt{cz Q3nFVdQ&MBb@0E)E^i~s-t literal 0 HcmV?d00001 diff --git a/assets/icon.png b/assets/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..963dcd613d9ef8c44119ac18467b7783e5d13dfd GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcwN$fBwreFf%hTyt{cz Q3nFVdQ&MBb@0E)E^i~s-t literal 0 HcmV?d00001 diff --git a/assets/splash.png b/assets/splash.png new file mode 100644 index 0000000000000000000000000000000000000000..963dcd613d9ef8c44119ac18467b7783e5d13dfd GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcwN$fBwreFf%hTyt{cz Q3nFVdQ&MBb@0E)E^i~s-t literal 0 HcmV?d00001 diff --git a/eas.json b/eas.json index 4277c79..4f7f683 100644 --- a/eas.json +++ b/eas.json @@ -13,7 +13,11 @@ "buildType": "apk" } }, - "production": {} + "production": { + "android": { + "buildType": "apk" + } + } }, "submit": { "production": {} diff --git a/package.json b/package.json index b4555aa..f591fa9 100644 --- a/package.json +++ b/package.json @@ -4,10 +4,14 @@ "description": "", "main": "node_modules/expo/AppEntry.js", "scripts": { - "start": "node src/cli/index.js", + "start": "expo start", + "start:cli": "node src/cli/index.js", "gui": "expo start", "android": "expo start --android", - "build:apk": "eas build --platform android --profile preview", + "web": "expo start --web", + "build:web": "expo export --platform web", + "build:apk": "eas build --platform android --profile preview --non-interactive", + "build:apk:production": "eas build --platform android --profile production --non-interactive", "test": "echo \"Error: no test specified\" && exit 1" }, "repository": { diff --git a/public/index.html b/public/index.html index 9933098..4411829 100644 --- a/public/index.html +++ b/public/index.html @@ -3,31 +3,158 @@ - ASTRA MATRIX | Awaken Your Spark + ASTRA MATRIX | Android Download + -

ASTRA MATRIX

-

The construct is fabricated. The Demiurge is watching.

- -

+
+

ASTRA MATRIX

+

The Android build is distributed as a signed APK package.

+

Use the button below on your Android device to download and install.

+ +
+ Release: latest
+ File: /download/astra-matrix-latest.apk +
+ + +

+ +
+

Install steps (Android)

+
    +
  1. Download the APK from this page.
  2. +
  3. Open the downloaded file in your browser's downloads.
  4. +
  5. If prompted, allow installs from this source.
  6. +
  7. Tap Install and launch ASTRA MATRIX.
  8. +
+
+ +
+

Troubleshooting

+
    +
  • If install is blocked, verify unknown app installation is allowed for your browser.
  • +
  • If Play Protect warns, confirm the APK came from your trusted domain.
  • +
  • Keep your Android version updated for best compatibility and security.
  • +
+
+
diff --git a/src/ai/NarrativeService.js b/src/ai/NarrativeService.js index ccea6ef..fd54e29 100644 --- a/src/ai/NarrativeService.js +++ b/src/ai/NarrativeService.js @@ -1,18 +1,74 @@ -const { GoogleGenerativeAI } = require('@google/generative-ai'); const state = require('../core/State'); -// Initialize with your API key environment variable -const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY || 'DUMMY_KEY'); - class NarrativeService { constructor() { - this.model = genAI.getGenerativeModel({ model: "gemini-1.5-flash" }); + this.model = null; + this.isAiEnabled = false; + this.lastFailureAt = null; + this.initializeModel(); + } + + initializeModel() { + const apiKey = process.env.GEMINI_API_KEY; + if (!apiKey || apiKey === 'DUMMY_KEY') { + console.warn('NarrativeService: GEMINI_API_KEY not set; enabling offline narrative mode'); + return; + } + + try { + const { GoogleGenerativeAI } = require('@google/generative-ai'); + const genAI = new GoogleGenerativeAI(apiKey); + this.model = genAI.getGenerativeModel({ model: "gemini-1.5-flash" }); + this.isAiEnabled = true; + } catch (error) { + console.error('NarrativeService: Failed to initialize AI model', error); + } + } + + buildOfflineScene(userAction, currentSceneText) { + const safeAction = (userAction || '').trim() || 'move forward'; + const contextPreview = (currentSceneText || '').split('\n')[0] || 'The matrix hums in silence.'; + return { + text: `${contextPreview}\n\nOffline mode engaged. Your intent "${safeAction}" echoes through the construct, and a new path appears.`, + choices: [ + { id: 'offline_observe', label: 'Observe the environment' }, + { id: 'offline_probe', label: 'Probe the system logs' }, + { id: 'offline_advance', label: 'Advance to the next sector' } + ], + xpReward: 25, + gnosisGain: 5 + }; + } + + normalizeScene(rawScene, fallbackScene) { + const scene = rawScene && typeof rawScene === 'object' ? rawScene : fallbackScene; + const safeChoices = Array.isArray(scene.choices) && scene.choices.length > 0 + ? scene.choices.slice(0, 3).map((choice, index) => ({ + id: choice.id || `choice_${index + 1}`, + label: choice.label || `Option ${index + 1}` + })) + : fallbackScene.choices; + + return { + text: scene.text || fallbackScene.text, + choices: safeChoices, + xpReward: Number.isFinite(scene.xpReward) ? scene.xpReward : fallbackScene.xpReward, + gnosisGain: Number.isFinite(scene.gnosisGain) ? scene.gnosisGain : fallbackScene.gnosisGain + }; } async generateNextScene(userAction, currentSceneText) { const aiName = state.getVar('aiName') || "Your Guide"; const currentLevel = state.player.level; const quarter = currentLevel <= 10 ? "Discovery" : currentLevel <= 20 ? "Resistance" : currentLevel <= 30 ? "Infiltration" : "Escape/Awakening"; + const fallbackScene = this.buildOfflineScene(userAction, currentSceneText); + + if (!this.isAiEnabled || !this.model) { + const scene = this.normalizeScene(fallbackScene, fallbackScene); + state.addXP(scene.xpReward); + state.player.gnosis += scene.gnosisGain; + return scene; + } const prompt = ` You are the entity "${aiName}". Guide the player (the divine spark) through the construct known as Reality ('toy-train town') created by the 'Demiurge'. @@ -39,13 +95,22 @@ class NarrativeService { "gnosisGain": 10 } `; - const result = await this.model.generateContent(prompt); - const response = await result.response; - const json = JSON.parse(response.text()); - - state.addXP(json.xpReward); - state.player.gnosis += (json.gnosisGain || 0); - return json; + try { + const result = await this.model.generateContent(prompt); + const response = await result.response; + const parsed = JSON.parse(response.text()); + const scene = this.normalizeScene(parsed, fallbackScene); + state.addXP(scene.xpReward); + state.player.gnosis += scene.gnosisGain; + return scene; + } catch (error) { + this.lastFailureAt = Date.now(); + console.error('NarrativeService: AI generation failed, using offline fallback', error); + const scene = this.normalizeScene(fallbackScene, fallbackScene); + state.addXP(scene.xpReward); + state.player.gnosis += scene.gnosisGain; + return scene; + } } } diff --git a/src/core/SceneEngine.js b/src/core/SceneEngine.js index 1b9b528..a8ab73e 100644 --- a/src/core/SceneEngine.js +++ b/src/core/SceneEngine.js @@ -44,7 +44,10 @@ class SceneEngine { async handleChoice(choice) { this.dynamicScene = null; if (choice.action === 'perform_restore') await corruption.performRestore(); - if (choice.action === 'sync_github') await github.authenticate('dummy-token'); + if (choice.action === 'sync_github') { + const githubToken = process.env.GITHUB_TOKEN || null; + await github.authenticate(githubToken); + } if (choice.action === 'apply_expansion') await github.applyExpansion(); if (choice.nextScene) { diff --git a/src/core/services/GitHubService.js b/src/core/services/GitHubService.js index 36e6670..429e732 100644 --- a/src/core/services/GitHubService.js +++ b/src/core/services/GitHubService.js @@ -1,16 +1,48 @@ -const { Octokit } = require('octokit'); -const simpleGit = require('simple-git'); const state = require('../State'); class GitHubService { constructor() { this.octokit = null; - this.git = simpleGit(); + this.git = null; + this.Octokit = null; + this.isNodeRuntime = typeof process !== 'undefined' && !!(process.versions && process.versions.node); this.repoOwner = "ALaustrup"; this.repoName = "App_369"; } + loadOctokit() { + if (this.Octokit) return this.Octokit; + try { + // Lazy import prevents React Native startup from pulling unsupported modules. + this.Octokit = require('octokit').Octokit; + return this.Octokit; + } catch (error) { + console.warn('GitHubService: Octokit unavailable in this runtime'); + return null; + } + } + + loadGitClient() { + if (!this.isNodeRuntime) return null; + if (this.git) return this.git; + try { + this.git = require('simple-git')(); + return this.git; + } catch (error) { + console.warn('GitHubService: simple-git unavailable in this runtime'); + return null; + } + } + async authenticate(token) { + if (!token || token === 'dummy-token') { + console.warn('GitHubService: Valid token not provided; running in offline mode'); + return false; + } + + const Octokit = this.loadOctokit(); + if (!Octokit) return false; + try { this.octokit = new Octokit({ auth: token }); const { data: user } = await this.octokit.rest.users.getAuthenticated(); @@ -37,10 +69,16 @@ class GitHubService { } async commitProgress(message) { + const git = this.loadGitClient(); + if (!git) { + console.warn('GitHubService: commitProgress not supported in this environment'); + return false; + } + try { - await this.git.add('.'); - await this.git.commit(message); - await this.git.push('origin', 'main'); + await git.add('.'); + await git.commit(message); + await git.push('origin', 'main'); console.log(`GitHubService: Progress committed - "${message}"`); return true; } catch (error) { From 7b4c7c316dfe733276a7a876c05c35e6e48d79c0 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 5 May 2026 06:55:51 +0000 Subject: [PATCH 02/12] Add APK release metadata and Vercel static landing config Co-authored-by: Andrew | Godmode --- public/download/latest.json | 9 ++++ public/index.html | 83 +++++++++++++++++++++++++++++++++++-- vercel.json | 21 +++++++++- 3 files changed, 108 insertions(+), 5 deletions(-) create mode 100644 public/download/latest.json diff --git a/public/download/latest.json b/public/download/latest.json new file mode 100644 index 0000000..84e7efc --- /dev/null +++ b/public/download/latest.json @@ -0,0 +1,9 @@ +{ + "version": "0.0.0-placeholder", + "buildDate": "1970-01-01T00:00:00.000Z", + "fileName": "astra-matrix-latest.apk", + "fileSizeBytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "minAndroidSdk": 24, + "releaseNotes": "Placeholder artifact. Upload a real APK to publish a release." +} diff --git a/public/index.html b/public/index.html index 4411829..6051e50 100644 --- a/public/index.html +++ b/public/index.html @@ -56,6 +56,12 @@ color: var(--text); font-size: 0.95rem; } + .version-grid { + display: grid; + grid-template-columns: auto 1fr; + gap: 8px 14px; + align-items: center; + } .btn { display: inline-block; margin-top: 20px; @@ -102,6 +108,9 @@ min-height: 1.2rem; color: var(--warning); } + .danger { + color: #ff8f8f; + } .hidden { display: none; } @@ -112,8 +121,19 @@

ASTRA MATRIX

Use the button below on your Android device to download and install.

- Release: latest
- File: /download/astra-matrix-latest.apk +
+ Release + loading... + Build date + loading... + File + /download/astra-matrix-latest.apk + Size + loading... + SHA256 + loading... +
+

@@ -140,21 +160,78 @@

Troubleshooting

diff --git a/vercel.json b/vercel.json index b438f58..e354a18 100644 --- a/vercel.json +++ b/vercel.json @@ -1,9 +1,26 @@ { "version": 2, "public": true, - "buildCommand": "npx expo export --platform web", - "outputDirectory": "dist", + "buildCommand": "echo \"Static landing deployment\"", + "outputDirectory": "public", + "headers": [ + { + "source": "/download/astra-matrix-latest.apk", + "headers": [ + { "key": "Content-Type", "value": "application/vnd.android.package-archive" }, + { "key": "Content-Disposition", "value": "attachment; filename=\"astra-matrix-latest.apk\"" }, + { "key": "Cache-Control", "value": "no-cache" } + ] + }, + { + "source": "/download/latest.json", + "headers": [ + { "key": "Cache-Control", "value": "no-store, must-revalidate" } + ] + } + ], "rewrites": [ + { "source": "/download/(.*)", "destination": "/download/$1" }, { "source": "/(.*)", "destination": "/index.html" } ] } From f2e2d4b6dfa2f822a7473447a129eff6b6b42233 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 5 May 2026 07:01:47 +0000 Subject: [PATCH 03/12] Add APK publish helper script and metadata workflow docs Co-authored-by: Andrew | Godmode --- DEPLOY_APK.md | 15 +++++++ public/download/latest.json | 2 +- scripts/publish-apk-release.sh | 71 ++++++++++++++++++++++++++++++++++ 3 files changed, 87 insertions(+), 1 deletion(-) create mode 100755 scripts/publish-apk-release.sh diff --git a/DEPLOY_APK.md b/DEPLOY_APK.md index 24e07c3..05fc34d 100644 --- a/DEPLOY_APK.md +++ b/DEPLOY_APK.md @@ -25,6 +25,21 @@ After `eas build` completes, download the generated APK and rename it: astra-matrix-latest.apk ``` +## 2.5) Publish artifact + metadata in one command + +Use the helper script to copy a real APK into the landing download path and generate checksum metadata: + +```bash +scripts/publish-apk-release.sh /path/to/your-real.apk 1.0.0 2026-05-05T08:00:00Z "Initial public APK release" +``` + +This updates: + +- `public/download/astra-matrix-latest.apk` +- `public/download/latest.json` + +The landing page reads `latest.json` to show version/build date/size/SHA256. + ## 3) Publish on your server Upload the APK to your web root so it is publicly available at: diff --git a/public/download/latest.json b/public/download/latest.json index 84e7efc..e29cf86 100644 --- a/public/download/latest.json +++ b/public/download/latest.json @@ -1,6 +1,6 @@ { "version": "0.0.0-placeholder", - "buildDate": "1970-01-01T00:00:00.000Z", + "buildDate": "1970-01-01T00:00:00Z", "fileName": "astra-matrix-latest.apk", "fileSizeBytes": 0, "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", diff --git a/scripts/publish-apk-release.sh b/scripts/publish-apk-release.sh new file mode 100755 index 0000000..38631b0 --- /dev/null +++ b/scripts/publish-apk-release.sh @@ -0,0 +1,71 @@ +#!/usr/bin/env bash +set -euo pipefail + +usage() { + cat <<'USAGE' +Usage: + scripts/publish-apk-release.sh [build_date_utc] [release_notes] + +Example: + scripts/publish-apk-release.sh ~/Downloads/app-release.apk 1.2.0 2026-05-05T07:30:00Z "Stability improvements" +USAGE +} + +if [ "${1:-}" = "-h" ] || [ "${1:-}" = "--help" ]; then + usage + exit 0 +fi + +if [ "$#" -lt 2 ]; then + usage + exit 1 +fi + +APK_SOURCE="$1" +VERSION="$2" +BUILD_DATE="${3:-$(date -u +"%Y-%m-%dT%H:%M:%SZ")}" +RELEASE_NOTES="${4:-Release ${VERSION}}" +TARGET_DIR="public/download" +TARGET_APK="${TARGET_DIR}/astra-matrix-latest.apk" +TARGET_META="${TARGET_DIR}/latest.json" + +if [ ! -f "${APK_SOURCE}" ]; then + echo "APK source file not found: ${APK_SOURCE}" >&2 + exit 1 +fi + +mkdir -p "${TARGET_DIR}" +if [ "$(realpath "${APK_SOURCE}")" != "$(realpath "${TARGET_APK}")" ]; then + cp "${APK_SOURCE}" "${TARGET_APK}" +fi + +FILE_SIZE_BYTES="$(wc -c < "${TARGET_APK}" | tr -d ' ')" +SHA256="$(sha256sum "${TARGET_APK}" | awk '{print $1}')" + +json_escape() { + printf '%s' "$1" | sed -e 's/\\/\\\\/g' -e 's/"/\\"/g' +} + +VERSION_ESCAPED="$(json_escape "${VERSION}")" +BUILD_DATE_ESCAPED="$(json_escape "${BUILD_DATE}")" +NOTES_ESCAPED="$(json_escape "${RELEASE_NOTES}")" + +cat > "${TARGET_META}" < Date: Tue, 5 May 2026 07:34:06 +0000 Subject: [PATCH 04/12] Trigger APK build after EXPO_TOKEN setup Co-authored-by: Andrew | Godmode From 8c32ea52d9a16b9f13f65d35d55255824e1661d4 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 5 May 2026 07:36:18 +0000 Subject: [PATCH 05/12] Initialize EAS project in CI before APK build Co-authored-by: Andrew | Godmode --- .github/workflows/build-apk.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/build-apk.yml b/.github/workflows/build-apk.yml index 29dafab..2d34135 100644 --- a/.github/workflows/build-apk.yml +++ b/.github/workflows/build-apk.yml @@ -30,5 +30,12 @@ jobs: - name: ๐Ÿ“ฆ Install dependencies run: npm ci + - name: ๐Ÿ”— Initialize EAS project (non-interactive) + env: + EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }} + run: eas init --non-interactive + - name: ๐Ÿš€ Build APK + env: + EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }} run: npm run build:apk From ed0144ec35f8e4be5db845a854d014f5d4debf76 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 5 May 2026 07:38:30 +0000 Subject: [PATCH 06/12] Force non-interactive EAS init in CI Co-authored-by: Andrew | Godmode --- .github/workflows/build-apk.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-apk.yml b/.github/workflows/build-apk.yml index 2d34135..a96fd9c 100644 --- a/.github/workflows/build-apk.yml +++ b/.github/workflows/build-apk.yml @@ -33,7 +33,7 @@ jobs: - name: ๐Ÿ”— Initialize EAS project (non-interactive) env: EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }} - run: eas init --non-interactive + run: eas init --non-interactive --force - name: ๐Ÿš€ Build APK env: From ced6c53e09731b4cc0ff28e62784b875860d0920 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 5 May 2026 07:44:41 +0000 Subject: [PATCH 07/12] Fix EAS owner config and replace valid APK build assets Co-authored-by: Andrew | Godmode --- .github/workflows/build-apk.yml | 5 ----- app.json | 1 + assets/adaptive-icon.png | Bin 68 -> 5704 bytes assets/favicon.png | Bin 68 -> 121 bytes assets/icon.png | Bin 68 -> 5684 bytes assets/splash.png | Bin 68 -> 15495 bytes 6 files changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/workflows/build-apk.yml b/.github/workflows/build-apk.yml index a96fd9c..c5825d5 100644 --- a/.github/workflows/build-apk.yml +++ b/.github/workflows/build-apk.yml @@ -30,11 +30,6 @@ jobs: - name: ๐Ÿ“ฆ Install dependencies run: npm ci - - name: ๐Ÿ”— Initialize EAS project (non-interactive) - env: - EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }} - run: eas init --non-interactive --force - - name: ๐Ÿš€ Build APK env: EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }} diff --git a/app.json b/app.json index d31ed86..69c0a6c 100644 --- a/app.json +++ b/app.json @@ -1,6 +1,7 @@ { "expo": { "name": "App_369", + "owner": "psybrninja", "slug": "app-369", "version": "1.0.0", "orientation": "portrait", diff --git a/assets/adaptive-icon.png b/assets/adaptive-icon.png index 963dcd613d9ef8c44119ac18467b7783e5d13dfd..fd819af5873208735d6d311d616d5f335eaa5da9 100644 GIT binary patch literal 5704 zcmeAS@N?(olHy`uVBq!ia0y~yU;#2&7&zE~RK2WrGXsMdzo(01NX4zUHw*^pnIIFZw~?|p60?C(VYEU3>vH(DV8ix}imH-Uiz)I%8*$pqD91k-u~P(86OCs>psDIg&%Ml(Csz#LGvznvhB%f=sMA4|_q5lnMwkNy(33wFFH>Qb3B~1OotF z0j&%tA)rU=1FV6ClmZAcX%&m$)Ixp352P|~Zci7-kcwN$fBwreFf%hTyt{cz Q3nFVdQ&MBb@0E)E^i~s-t diff --git a/assets/favicon.png b/assets/favicon.png index 963dcd613d9ef8c44119ac18467b7783e5d13dfd..73d9b5f0379af2710692a6b75a9b5440ce2ec84d 100644 GIT binary patch literal 121 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTC2TvErkcwMx&l~bGFmN2$@Y9@E ntirP@#9{R_OGbjAaW-pI|M$ujEusBDvlu*G{an^LB{Ts5pgS1V literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcwN$fBwreFf%hTyt{cz Q3nFVdQ&MBb@0E)E^i~s-t diff --git a/assets/icon.png b/assets/icon.png index 963dcd613d9ef8c44119ac18467b7783e5d13dfd..cb5a239099f39b293928e19504d299b244fa4166 100644 GIT binary patch literal 5684 zcmeHLu}TCn5S=>?Sq`?a5JV6TTd}YZu~S*mZ4MEwZ1M?Lij9jl+W7^Rmf|Pe7g$+{ ze_$cnijA3EVMxLxQw&UTf!hsX=FOXz$?Wak9gh#|;mQyI>h`EP0T|fn0ICIhyINhp z0a*HNo4wPE_s_S@8laj!|7_R&$?oOr^ySPh!1qICug3lW&?`S(Mysxe*X*YzPbJbD>N84 literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcwN$fBwreFf%hTyt{cz Q3nFVdQ&MBb@0E)E^i~s-t diff --git a/assets/splash.png b/assets/splash.png index 963dcd613d9ef8c44119ac18467b7783e5d13dfd..48468299493a684eda3e8ec297e26b27c068a3ff 100644 GIT binary patch literal 15495 zcmeHOF>6#o5FT@fCc%>&>4dAS?9L*oEH3fsd4)%mI~xn_v`HBeEIiakh`&Gsf{j%` zNM|E9Rw+b;aDwCq2sW0AzJ2?I8E)oz`!e_5zOX5BmoK|Jv-5p3ySIDq*+#$HSUkH3 z0FB<&=mtQ246yKY{$z}NKBzSSF7|uT+Tg+X`@w}10H>d{f4%q?{oHTXUJhDuBWN$j zFu?X|@<-eo;QhUtd10W}@qlp`+{!|>ZQ2n-wk=FTIgFHL*L`w2^L4kI5!7pSfjaM^ z+A)rDBJe>)ZvK?c&aD^`r9gk{Dp1`-D;#M81O&N%iR~uoeNzMlp)89!Eigsn$ zRp+?Gy9BWgkz5s_93OvF>0hnP9%3bw9eXS}9slFEm*&h#=-Qv#?><~P99_p}@8U`8 zQ4U{?HEe{O1LFf?(uy1q<5p?+aP#)R2pxx;w{YQ)rSFs=BK<@+`iUvpwKHL}KV10h z^MHjzg6|rI{wdCj=J~X{CIf&aOah&61~g)Xm|b2 FZci7-kcwN$fBwreFf%hTyt{cz Q3nFVdQ&MBb@0E)E^i~s-t From 91ac42801c322f9da8a325b62180f9b86a743d45 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 5 May 2026 07:46:53 +0000 Subject: [PATCH 08/12] Add Expo prebuild validation step to CI Co-authored-by: Andrew | Godmode --- .github/workflows/build-apk.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/build-apk.yml b/.github/workflows/build-apk.yml index c5825d5..f93278b 100644 --- a/.github/workflows/build-apk.yml +++ b/.github/workflows/build-apk.yml @@ -30,6 +30,11 @@ jobs: - name: ๐Ÿ“ฆ Install dependencies run: npm ci + - name: ๐Ÿงช Validate Expo prebuild + env: + EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }} + run: npx expo prebuild --platform android --non-interactive --no-install + - name: ๐Ÿš€ Build APK env: EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }} From e5af7bcfa3f0acf63777311810137458880ad4d6 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 5 May 2026 07:48:54 +0000 Subject: [PATCH 09/12] Pin EAS project ID for non-interactive CI builds Co-authored-by: Andrew | Godmode --- app.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app.json b/app.json index 69c0a6c..5872721 100644 --- a/app.json +++ b/app.json @@ -27,6 +27,11 @@ }, "web": { "favicon": "./assets/favicon.png" + }, + "extra": { + "eas": { + "projectId": "440a38e6-afe8-42f9-82a5-f9f987e5c44d" + } } } } From 69e19180e8eccfbab397b43b9501f55a44be4b8e Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 5 May 2026 07:53:30 +0000 Subject: [PATCH 10/12] Guard mobile bundle from Node-only runtime imports Co-authored-by: Andrew | Godmode --- src/ai/NarrativeService.js | 9 +++++++++ src/core/services/GitHubService.js | 19 ++++++++++++++++--- src/core/services/Web3Service.js | 17 +++++++++++++++-- 3 files changed, 40 insertions(+), 5 deletions(-) diff --git a/src/ai/NarrativeService.js b/src/ai/NarrativeService.js index fd54e29..a205608 100644 --- a/src/ai/NarrativeService.js +++ b/src/ai/NarrativeService.js @@ -8,7 +8,16 @@ class NarrativeService { this.initializeModel(); } + isNativeRuntime() { + return typeof navigator !== 'undefined' && navigator.product === 'ReactNative'; + } + initializeModel() { + if (this.isNativeRuntime()) { + console.warn('NarrativeService: Native runtime detected; AI SDK disabled for build safety'); + return; + } + const apiKey = process.env.GEMINI_API_KEY; if (!apiKey || apiKey === 'DUMMY_KEY') { console.warn('NarrativeService: GEMINI_API_KEY not set; enabling offline narrative mode'); diff --git a/src/core/services/GitHubService.js b/src/core/services/GitHubService.js index 429e732..e57e0e7 100644 --- a/src/core/services/GitHubService.js +++ b/src/core/services/GitHubService.js @@ -1,5 +1,14 @@ const state = require('../State'); +function getRuntimeRequire() { + try { + // Using eval avoids static module resolution in Metro during mobile bundling. + return eval('require'); + } catch (error) { + return null; + } +} + class GitHubService { constructor() { this.octokit = null; @@ -11,10 +20,12 @@ class GitHubService { } loadOctokit() { + if (!this.isNodeRuntime) return null; if (this.Octokit) return this.Octokit; try { - // Lazy import prevents React Native startup from pulling unsupported modules. - this.Octokit = require('octokit').Octokit; + const runtimeRequire = getRuntimeRequire(); + if (!runtimeRequire) return null; + this.Octokit = runtimeRequire('octokit').Octokit; return this.Octokit; } catch (error) { console.warn('GitHubService: Octokit unavailable in this runtime'); @@ -26,7 +37,9 @@ class GitHubService { if (!this.isNodeRuntime) return null; if (this.git) return this.git; try { - this.git = require('simple-git')(); + const runtimeRequire = getRuntimeRequire(); + if (!runtimeRequire) return null; + this.git = runtimeRequire('simple-git')(); return this.git; } catch (error) { console.warn('GitHubService: simple-git unavailable in this runtime'); diff --git a/src/core/services/Web3Service.js b/src/core/services/Web3Service.js index c3850ee..cd08e76 100644 --- a/src/core/services/Web3Service.js +++ b/src/core/services/Web3Service.js @@ -1,17 +1,30 @@ -const { ethers } = require('ethers'); const state = require('../State'); class Web3Service { constructor() { this.provider = null; this.signer = null; + this.ethers = null; + } + + loadEthers() { + if (this.ethers) return this.ethers; + try { + this.ethers = require('ethers'); + return this.ethers; + } catch (error) { + console.warn('Web3Service: ethers unavailable in this runtime'); + return null; + } } async connectWallet(privateKey = null) { try { if (privateKey) { + const ethersLib = this.loadEthers(); + if (!ethersLib) return null; // CLI / Manual private key connection - const wallet = new ethers.Wallet(privateKey); + const wallet = new ethersLib.Wallet(privateKey); state.player.walletAddress = wallet.address; this.signer = wallet; return wallet.address; From be0e4fe46af6b5d61ca79be53e36f3f0975e7712 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 5 May 2026 07:58:01 +0000 Subject: [PATCH 11/12] Avoid static SDK requires during Android bundle Co-authored-by: Andrew | Godmode --- src/ai/NarrativeService.js | 13 ++++++++++++- src/core/services/Web3Service.js | 12 +++++++++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/ai/NarrativeService.js b/src/ai/NarrativeService.js index a205608..fd5fcea 100644 --- a/src/ai/NarrativeService.js +++ b/src/ai/NarrativeService.js @@ -1,5 +1,14 @@ const state = require('../core/State'); +function getRuntimeRequire() { + try { + // Prevent Metro from statically resolving Node-oriented SDK packages. + return eval('require'); + } catch (error) { + return null; + } +} + class NarrativeService { constructor() { this.model = null; @@ -25,7 +34,9 @@ class NarrativeService { } try { - const { GoogleGenerativeAI } = require('@google/generative-ai'); + const runtimeRequire = getRuntimeRequire(); + if (!runtimeRequire) return; + const { GoogleGenerativeAI } = runtimeRequire('@google/generative-ai'); const genAI = new GoogleGenerativeAI(apiKey); this.model = genAI.getGenerativeModel({ model: "gemini-1.5-flash" }); this.isAiEnabled = true; diff --git a/src/core/services/Web3Service.js b/src/core/services/Web3Service.js index cd08e76..cdf4e88 100644 --- a/src/core/services/Web3Service.js +++ b/src/core/services/Web3Service.js @@ -1,5 +1,13 @@ const state = require('../State'); +function getRuntimeRequire() { + try { + return eval('require'); + } catch (error) { + return null; + } +} + class Web3Service { constructor() { this.provider = null; @@ -10,7 +18,9 @@ class Web3Service { loadEthers() { if (this.ethers) return this.ethers; try { - this.ethers = require('ethers'); + const runtimeRequire = getRuntimeRequire(); + if (!runtimeRequire) return null; + this.ethers = runtimeRequire('ethers'); return this.ethers; } catch (error) { console.warn('Web3Service: ethers unavailable in this runtime'); From 01700665d8e41c0b77eec2a9c9a05b898cfc6c35 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 5 May 2026 08:01:49 +0000 Subject: [PATCH 12/12] Remove prebuild diagnostic step from APK CI Co-authored-by: Andrew | Godmode --- .github/workflows/build-apk.yml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.github/workflows/build-apk.yml b/.github/workflows/build-apk.yml index f93278b..c5825d5 100644 --- a/.github/workflows/build-apk.yml +++ b/.github/workflows/build-apk.yml @@ -30,11 +30,6 @@ jobs: - name: ๐Ÿ“ฆ Install dependencies run: npm ci - - name: ๐Ÿงช Validate Expo prebuild - env: - EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }} - run: npx expo prebuild --platform android --non-interactive --no-install - - name: ๐Ÿš€ Build APK env: EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }}