Files
Claude 892d778b08 feat(termux): F-Droid deep-link + RUN_COMMAND_PENDING_INTENT install path
Make the on-device Termux install flow actually work end-to-end. Three
workstreams from plans/atomic-wondering-sunrise.md plus a validation
patch:

Workstream A — F-Droid deep-link from WelcomeScreen
- The 'Install locally (Termux)' card no longer renders a dead disabled
  Continue button when Termux is missing. It now shows two stacked CTAs:
  primary 'Open F-Droid' (existing termux_install_action string, now
  actually used) deep-linking https://f-droid.org/packages/com.termux/,
  and secondary 'Or get it from GitHub Releases' (new string) for
  side-loaders deep-linking github.com/termux/termux-app/releases.
- Reuses existing HermesApi.openExternal; no new IPC method needed.

Workstream B — HermesInstaller backend-aware shell dispatch
- runShell / runHermesDoctor / runPipInstall in HermesInstaller now
  branch on currentBackend() and dispatch to TermuxRunner on Termux,
  bundled.runPython on Bundled. Pre-fix, every stage shelled through
  bundled.runPython regardless of backend, so Termux installs failed
  immediately at stage 3 with 'Bundled Python not available'.

Workstream C — RUN_COMMAND_PENDING_INTENT for real exit codes
- New TermuxResultReceiver.kt: BroadcastReceiver + TermuxResultRegistry
  process-singleton. Atomic session IDs, ConcurrentHashMap of
  Continuations, PendingIntent.FLAG_MUTABLE result intent builder
  (FLAG_MUTABLE is required — Termux's RunCommandService adds the
  result bundle to the PendingIntent before broadcasting it back).
- New TermuxRunner.runAndWait(command, cwd): suspend fn using
  suspendCancellableCoroutine (modeled on GatewayClient.request),
  attaches the result PendingIntent via the
  com.termux.RUN_COMMAND_PENDING_INTENT intent extra. Resolves with a
  BundledPythonRunner.PythonResult carrying the real exit code +
  truncated stdout/stderr (~100KB cap imposed by Termux).
- HermesInstaller.runShellViaTermux switches from termux.run to
  termux.runAndWait. The 4 call sites (stages 3/4/5/8 in runStages,
  plus runHermesUpdate) already gate on exitCode != 0 and consume
  stderr/stdout, so they become correct automatically.
- applyPatches rewritten as suspend: dispatches git apply via Termux
  using a bash heredoc to write the patch content to Termux's
  $PREFIX/tmp/ (our app's process can't write into Termux's sandbox).
  Drops the now-stale TODO(workstream-B-followup) comment.
- AndroidManifest gets a <queries> block declaring Termux package
  visibility — without it, on API 30+, PackageManager.getPackageInfo
  returns NameNotFoundException even when Termux is installed and
  HermesInstaller falls through to the bundled-Python backend.
  Also adds com.termux.permission.RUN_COMMAND (user-granted in
  Android Settings -> App info -> Additional permissions).
- TermuxProbe.isRunCommandResultSupported() helper checks Termux
  versionName >= 0.109 (the floor for RUN_COMMAND_PENDING_INTENT).
- InstallScreen surfaces a TermuxPermissionNeededCard when an install
  stage fails with a Termux-specific error pattern (RUN_COMMAND,
  allow-external-apps, plugin_action_disabled — NOT the bare word
  'permission' which pip prints routinely). The card has an 'Open
  App Settings' button using a new HermesApi.openAppSettings()
  helper that fires Settings.ACTION_APPLICATION_DETAILS_SETTINGS.
- setup-android.sh gains a queries-merge step. Anchored on
  start-of-line for awk and probes android:name="com.termux" for
  detect-and-skip (so a future library's unrelated <queries> block
  doesn't silently skip the Termux merge).

Validation patch — markerDir + Termux sandbox awareness (B1–B5)
- An adversarial review found that HermesInstaller assumed every path
  it computes (hermesPython, hermesRepo, hermesVenv, the verified
  marker) lives somewhere our app can read. On Termux backend
  hermesHome resolves to /data/data/com.termux/files/home/.hermes
  which is OFF-LIMITS to our process. Pre-Workstream-C the install
  never succeeded on Termux so these checks were never exercised
  against real state; Workstream C made the install actually work,
  so the broken file-existence checks now blocked every user from
  ever reaching AppState.Main.
- Fix: backend-aware markerDir at context.filesDir/hermes-markers/
  on Termux (hermesHome on Bundled). New sentinels
  hermesVerifiedMarker / hermesRepoClonedMarker /
  hermesVenvCreatedMarker live there. Stages 3 & 4 write their
  marker on success and check the marker (not the file) for
  skip-on-rerun. checkInstall().installed/configured/hasApiKey
  collapse to 'verified marker exists' on Termux (the marker IS
  the success signal of a completed install).
- Stage 4 switches from python3.11 to python on Termux — Termux's
  pkg install python ships the binary as 'python' symlinked to the
  current 3.x; the literal 'python3.11' was a Bundled-path
  assumption that happened to work on some Termux setups but not
  others.
- getHermesVersion becomes suspend + backend-aware (Termux dispatches
  the importlib.metadata.version call via runAndWait); HermesApi
  cascades.

Plan + research artifacts
- plans/atomic-wondering-sunrise.md is the live source-of-truth
  plan/postmortem (3 shipped workstreams + validation section).
- docs/python-bundling-alternatives.md captures the research that
  showed Termux + RESULT_INTENT is the right v1 path and that the
  originally-planned Chaquopy workstream isn't needed. Alpine
  minirootfs + proot is the recommended Tier 2 alternative for
  users without Termux, downloaded on first run (no APK bloat, no
  GPL contagion, F-Droid compatible).

Build status
- ./gradlew :app:assembleDebug green (arm64-v8a, armeabi-v7a, x86_64,
  universal APKs, ~20MB each).
- script/check-kotlin-drift.sh green (59 Kotlin files in sync).
- graphify graph rebuilt (1681 nodes, 2866 edges).

Untested on device. Verification steps for happy path,
permission-denied regression, and cancellation hygiene are in the
plan's Workstream C verification section.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 18:54:41 -04:00

69 lines
996 B
Plaintext

# Project metadata
.claude/
CLAUDE.md
review/
# Dependencies
node_modules/
.pnpm-store/
# Build artifacts
dist/
build/
out/
*.tsbuildinfo
# Capacitor / Android
apps/mobile/android/.gradle/
apps/mobile/android/.idea/
apps/mobile/android/local.properties
apps/mobile/android/app/build/
apps/mobile/android/captures/
apps/mobile/android/app/release/
apps/mobile/android/app/debug/
apps/mobile/android/*.iml
apps/mobile/android/.cxx/
# iOS (not used in v1, scaffolded for future)
apps/mobile/ios/Pods/
apps/mobile/ios/build/
# Generated renderer
packages/renderer/dist/
# Keystore and signing material
*.jks
*.keystore
keystore.properties
android-runner/app/release.keystore
# Logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# OS files
.DS_Store
Thumbs.db
# Editor
.vscode/*
!.vscode/extensions.json
.idea/
# Env
.env
.env.local
.env.*.local
# Python artifacts (vendored hermes-agent install)
__pycache__/
*.pyc
.pytest_cache/
.venv/
venv/
# Sensitive
.sentryclirc
graphify-out/