Vendor OpenClaw source as Adolf fork baseline
Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled

Adolf is a fork/vendored clone of github.com/openclaw/openclaw (v2026.6.11),
free to diverge. Tree copied sans upstream .git; upstream remote added for
future syncs. Node pinned to 24 (.nvmrc); engines already require >=22.19.
Preserves docs/ARCHITECTURE.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LeqyaxJF2nbRXJtae2kNB2
This commit is contained in:
2026-07-05 09:36:54 +00:00
parent 3216769225
commit bedb527145
21108 changed files with 6010766 additions and 0 deletions

View File

@@ -0,0 +1,20 @@
# Google Play API key (pick one approach)
#
# Recommended local path:
# GOOGLE_PLAY_JSON_KEY=/absolute/path/to/google-play-service-account.json
#
# Or raw JSON content for CI:
# GOOGLE_PLAY_JSON_KEY_DATA={"type":"service_account",...}
# Optional app targeting
# GOOGLE_PLAY_PACKAGE_NAME=ai.openclaw.app
# Release target
# GOOGLE_PLAY_TRACK=internal
# GOOGLE_PLAY_RELEASE_STATUS=completed
# GOOGLE_PLAY_VALIDATE_ONLY=1
# Metadata toggles
# SUPPLY_UPLOAD_METADATA=1
# SUPPLY_UPLOAD_IMAGES=1
# SUPPLY_UPLOAD_SCREENSHOTS=1

View File

@@ -0,0 +1,3 @@
package_name(ENV["GOOGLE_PLAY_PACKAGE_NAME"] || "ai.openclaw.app")
json_key_file(ENV["GOOGLE_PLAY_JSON_KEY"]) if ENV["GOOGLE_PLAY_JSON_KEY"]

View File

@@ -0,0 +1,458 @@
require "fileutils"
require "json"
require "open3"
require "shellwords"
require "supply/client"
default_platform(:android)
ANDROID_FASTLANE_ROOT = File.expand_path(__dir__, Dir.pwd)
DEFAULT_PLAY_PACKAGE_NAME = "ai.openclaw.app"
DEFAULT_PLAY_TRACK = "internal"
DEFAULT_PLAY_RELEASE_STATUS = "completed"
ANDROID_RELEASE_SIGNING_GRADLE_PROPERTIES = [
"OPENCLAW_ANDROID_STORE_FILE",
"OPENCLAW_ANDROID_STORE_PASSWORD",
"OPENCLAW_ANDROID_KEY_ALIAS",
"OPENCLAW_ANDROID_KEY_PASSWORD"
].freeze
def load_env_file(path)
return unless File.exist?(path)
File.foreach(path) do |line|
stripped = line.strip
next if stripped.empty? || stripped.start_with?("#")
key, value = stripped.split("=", 2)
next if key.nil? || key.empty? || value.nil?
ENV[key] = value if ENV[key].nil? || ENV[key].strip.empty?
end
end
def env_present?(value)
!value.nil? && !value.strip.empty?
end
def android_root
File.expand_path("..", ANDROID_FASTLANE_ROOT)
end
def repo_root
File.expand_path("../..", android_root)
end
def android_release_signing_script
File.join(repo_root, "scripts", "android-release-signing.mjs")
end
def android_release_signing_materialized_properties_path
File.join(android_root, "build", "release-signing", "gradle.properties")
end
def shell_join(args)
args.shelljoin
end
def play_package_name
raw = ENV["GOOGLE_PLAY_PACKAGE_NAME"].to_s.strip
raw.empty? ? DEFAULT_PLAY_PACKAGE_NAME : raw
end
def play_track
raw = ENV["GOOGLE_PLAY_TRACK"].to_s.strip
raw.empty? ? DEFAULT_PLAY_TRACK : raw
end
def play_release_status
raw = ENV["GOOGLE_PLAY_RELEASE_STATUS"].to_s.strip
raw.empty? ? DEFAULT_PLAY_RELEASE_STATUS : raw
end
def play_validate_only?
ENV["GOOGLE_PLAY_VALIDATE_ONLY"] == "1"
end
def play_metadata_upload_requested?
ENV["SUPPLY_UPLOAD_METADATA"] == "1"
end
def play_screenshot_upload_requested?
ENV["SUPPLY_UPLOAD_SCREENSHOTS"] == "1"
end
def play_image_upload_requested?
ENV["SUPPLY_UPLOAD_IMAGES"] == "1"
end
def play_auth_options
json_key = ENV["GOOGLE_PLAY_JSON_KEY"].to_s.strip
json_key = ENV["SUPPLY_JSON_KEY"].to_s.strip if json_key.empty?
json_key = ENV["GOOGLE_PLAY_JSON_KEY_PATH"].to_s.strip if json_key.empty?
return { json_key: json_key } unless json_key.empty?
json_key_data = ENV["GOOGLE_PLAY_JSON_KEY_DATA"].to_s.strip
json_key_data = ENV["SUPPLY_JSON_KEY_DATA"].to_s.strip if json_key_data.empty?
return { json_key_data: json_key_data } unless json_key_data.empty?
UI.user_error!("Missing Google Play API credentials. Set GOOGLE_PLAY_JSON_KEY or GOOGLE_PLAY_JSON_KEY_DATA.")
end
def validate_play_auth!
client = nil
begin
client = Supply::Client.make_from_config(params: play_auth_options)
client.begin_edit(package_name: play_package_name)
rescue => e
UI.user_error!("Google Play API credentials are invalid for #{play_package_name}: #{e.message}")
ensure
if client&.current_edit
begin
client.abort_current_edit
rescue => e
UI.user_error!("Google Play API credentials opened a validation edit but could not close it: #{e.message}")
end
end
end
end
def read_android_version_metadata
stdout, stderr, status = Open3.capture3(
"node",
"--import",
"tsx",
File.join(repo_root, "scripts", "android-version.ts"),
"--json",
"--root",
repo_root
)
unless status.success?
detail = stderr.to_s.strip
detail = stdout.to_s.strip if detail.empty?
UI.user_error!("Failed to read Android version metadata: #{detail}")
end
parsed = JSON.parse(stdout)
version = parsed.fetch("canonicalVersion").to_s
version_code = parsed.fetch("versionCode").to_i
UI.user_error!("Android version helper returned incomplete metadata.") if version.empty? || version_code <= 0
{ version: version, version_code: version_code }
rescue JSON::ParserError => e
UI.user_error!("Invalid JSON from Android version helper: #{e.message}")
end
def sync_android_versioning!
sh(shell_join(["node", "--import", "tsx", File.join(repo_root, "scripts", "android-sync-versioning.ts"), "--check", "--root", repo_root]))
end
def android_release_notes_path
File.join(ANDROID_FASTLANE_ROOT, "metadata", "android", "en-US", "release_notes.txt")
end
def validate_android_release_notes!
release_notes_path = android_release_notes_path
UI.user_error!("Missing Android release notes at #{release_notes_path}. Run `pnpm android:version:sync`.") unless File.exist?(release_notes_path)
UI.user_error!("Android release notes at #{release_notes_path} are empty.") unless env_present?(File.read(release_notes_path))
end
def android_changelog_path(version_code)
File.join(ANDROID_FASTLANE_ROOT, "metadata", "android", "en-US", "changelogs", "#{version_code}.txt")
end
def sync_android_changelog!(version_code)
validate_android_release_notes!
changelog_path = android_changelog_path(version_code)
FileUtils.mkdir_p(File.dirname(changelog_path))
File.write(changelog_path, File.read(android_release_notes_path))
changelog_path
end
def play_metadata_path
File.join(ANDROID_FASTLANE_ROOT, "metadata", "android")
end
def play_screenshot_paths
Dir[File.join(play_metadata_path, "**", "images", "**", "*.png")]
end
def validate_android_screenshots!
return unless play_screenshot_upload_requested?
if play_screenshot_paths.empty?
UI.user_error!("SUPPLY_UPLOAD_SCREENSHOTS=1 but no PNG screenshots were found under apps/android/fastlane/metadata/android/*/images.")
end
end
def release_artifact_path(version)
File.join(android_root, "build", "release-artifacts", "openclaw-#{version}-play-release.aab")
end
def build_release_artifacts!
sh(shell_join(["bun", File.join(android_root, "scripts", "build-release-artifacts.ts")]))
end
def capture_android_screenshots!
sh(shell_join(["bash", File.join(repo_root, "scripts", "android-screenshots.sh")]))
end
def mobile_release_ref_script
File.join(repo_root, "scripts", "mobile-release-ref.ts")
end
def release_git_sha
stdout, stderr, status = Open3.capture3("git", "rev-parse", "HEAD", chdir: repo_root)
UI.user_error!("Unable to resolve release Git SHA: #{stderr.strip}") unless status.success?
stdout.strip
end
def mobile_release_ref_command(command, platform:, version:, build: nil, version_code: nil, sha: nil)
args = [
"node",
"--import",
"tsx",
mobile_release_ref_script,
command,
"--platform",
platform,
"--version",
version,
"--root",
repo_root,
]
args.push("--build", build.to_s) if build
args.push("--version-code", version_code.to_s) if version_code
args.push("--sha", sha.to_s) if sha
sh(shell_join(args))
end
def ensure_mobile_release_ref_available!(platform:, version:, build: nil, version_code: nil, sha: nil)
mobile_release_ref_command(
"preflight",
platform: platform,
version: version,
build: build,
version_code: version_code,
sha: sha
)
end
def record_mobile_release_ref!(platform:, version:, build: nil, version_code: nil, sha: nil)
mobile_release_ref_command(
"record",
platform: platform,
version: version,
build: build,
version_code: version_code,
sha: sha
)
end
def read_android_release_signing_properties!(path)
UI.user_error!("Missing materialized Android release signing properties at #{path}.") unless File.exist?(path)
properties = {}
File.foreach(path) do |line|
stripped = line.strip
next if stripped.empty? || stripped.start_with?("#")
key, value = stripped.split("=", 2)
next if key.nil? || key.empty? || value.nil?
properties[key] = value.strip
end
missing = ANDROID_RELEASE_SIGNING_GRADLE_PROPERTIES.reject { |key| env_present?(properties[key]) }
UI.user_error!("Materialized Android release signing properties are missing: #{missing.join(', ')}.") unless missing.empty?
properties
end
def export_android_release_signing_properties!(path)
read_android_release_signing_properties!(path).each do |key, value|
ENV["ORG_GRADLE_PROJECT_#{key}"] = value
end
end
def sync_android_release_signing!
sh(shell_join(["node", android_release_signing_script, "--mode", "sync-pull"]))
export_android_release_signing_properties!(android_release_signing_materialized_properties_path)
end
def prepare_android_release_signing!
if env_present?(ENV["MATCH_PASSWORD"])
sync_android_release_signing!
elsif File.exist?(android_release_signing_materialized_properties_path)
export_android_release_signing_properties!(android_release_signing_materialized_properties_path)
end
end
def validate_android_release_signing!
Dir.chdir(android_root) do
sh(shell_join(["./gradlew", ":app:bundlePlayRelease", "--dry-run"]))
end
end
def print_android_release_plan!(version_metadata)
UI.message("Android Play release plan:")
UI.message(" package: #{play_package_name}")
UI.message(" track: #{play_track}")
UI.message(" release_status: #{play_release_status}")
UI.message(" validate_only: #{play_validate_only?}")
UI.message(" versionName: #{version_metadata.fetch(:version)}")
UI.message(" versionCode: #{version_metadata.fetch(:version_code)}")
end
def validate_android_release_preflight!(version_metadata)
validate_play_auth!
prepare_android_release_signing!
validate_android_release_signing!
validate_android_release_notes!
print_android_release_plan!(version_metadata)
end
def upload_play_store_metadata!(version_metadata)
validate_android_screenshots!
sync_android_changelog!(version_metadata.fetch(:version_code))
upload_to_play_store(
**play_auth_options,
package_name: play_package_name,
track: play_track,
version_code: version_metadata.fetch(:version_code),
metadata_path: play_metadata_path,
skip_upload_apk: true,
skip_upload_aab: true,
skip_upload_metadata: !play_metadata_upload_requested?,
skip_upload_changelogs: false,
skip_upload_images: !play_image_upload_requested?,
skip_upload_screenshots: !play_screenshot_upload_requested?,
validate_only: play_validate_only?
)
end
def upload_play_store_build!(version_metadata, upload_metadata: false, upload_images: false, upload_screenshots: false)
release_sha = release_git_sha
ensure_mobile_release_ref_available!(
platform: "android",
version: version_metadata.fetch(:version),
version_code: version_metadata.fetch(:version_code),
sha: release_sha
)
ENV["SUPPLY_UPLOAD_SCREENSHOTS"] = "1" if upload_screenshots
validate_android_screenshots!
sync_android_changelog!(version_metadata.fetch(:version_code))
artifact_path = release_artifact_path(version_metadata.fetch(:version))
UI.user_error!("Missing Play release artifact at #{artifact_path}. Run pnpm android:release:archive first.") unless File.exist?(artifact_path)
upload_to_play_store(
**play_auth_options,
package_name: play_package_name,
aab: artifact_path,
track: play_track,
release_status: play_release_status,
metadata_path: play_metadata_path,
skip_upload_apk: true,
skip_upload_metadata: !upload_metadata,
skip_upload_changelogs: false,
skip_upload_images: !upload_images,
skip_upload_screenshots: !upload_screenshots,
validate_only: play_validate_only?
)
unless play_validate_only?
record_mobile_release_ref!(
platform: "android",
version: version_metadata.fetch(:version),
version_code: version_metadata.fetch(:version_code),
sha: release_sha
)
end
end
load_env_file(File.join(ANDROID_FASTLANE_ROOT, ".env"))
platform :android do
desc "Validate Google Play API credentials"
lane :auth_check do
validate_play_auth!
UI.success("Google Play API credentials are valid.")
end
desc "Print the Android release signing plan"
lane :signing_plan do
sh(shell_join(["node", android_release_signing_script, "--mode", "plan"]))
end
desc "Pull encrypted Android release signing assets and validate Gradle release signing"
lane :signing_check do
sync_android_release_signing!
validate_android_release_signing!
UI.success("Android release signing assets are available locally.")
end
desc "Pull encrypted Android release signing assets from the shared signing repo"
lane :signing_sync_pull do
sync_android_release_signing!
UI.success("Pulled Android release signing assets.")
end
desc "Create or refresh encrypted Android release signing assets in the shared signing repo"
lane :signing_sync_push do
sh(shell_join(["node", android_release_signing_script, "--mode", "sync-push"]))
UI.success("Pushed Android release signing assets.")
end
desc "Validate Android Play release auth, signing, versioning, and release notes"
lane :release_preflight do
sync_android_versioning!
version_metadata = read_android_version_metadata
validate_android_release_preflight!(version_metadata)
UI.success("Android Play release preflight passed for #{version_metadata[:version]} (#{version_metadata[:version_code]}).")
end
desc "Upload Google Play metadata, changelog, and optional screenshots"
lane :metadata do
sync_android_versioning!
version_metadata = read_android_version_metadata
ENV["SUPPLY_UPLOAD_METADATA"] = "1" unless ENV.key?("SUPPLY_UPLOAD_METADATA")
upload_play_store_metadata!(version_metadata)
UI.success("Uploaded Android Play metadata for #{version_metadata[:version]} (#{version_metadata[:version_code]}).")
end
desc "Build signed Android release artifacts locally without uploading"
lane :play_store_archive do
sync_android_versioning!
prepare_android_release_signing!
build_release_artifacts!
end
desc "Generate deterministic Android screenshots for Google Play metadata"
lane :screenshots do
capture_android_screenshots!
end
desc "Upload the signed Play AAB to Google Play"
lane :play_store do
sync_android_versioning!
version_metadata = read_android_version_metadata
upload_play_store_build!(version_metadata)
UI.success("Uploaded Android Play build to #{play_track}: version=#{version_metadata[:version]} code=#{version_metadata[:version_code]}")
end
desc "Upload Android metadata, archive release artifacts, then upload the Play AAB"
lane :release_upload do
sync_android_versioning!
version_metadata = read_android_version_metadata
validate_android_release_preflight!(version_metadata)
screenshots
ENV["SUPPLY_UPLOAD_METADATA"] = "1"
ENV["SUPPLY_UPLOAD_SCREENSHOTS"] = "1"
build_release_artifacts!
upload_play_store_build!(version_metadata, upload_metadata: true, upload_screenshots: true)
UI.success("Uploaded Android Play build to #{play_track}: version=#{version_metadata[:version]} code=#{version_metadata[:version_code]}")
UI.important("Production promotion remains manual in Google Play Console.")
end
end

View File

@@ -0,0 +1,123 @@
# fastlane setup (OpenClaw Android)
Install:
```bash
brew install fastlane
```
Create a Google Play service account JSON key with Google Play Developer API access, then grant that service account access to the OpenClaw app in Play Console.
Recommended local auth:
```bash
GOOGLE_PLAY_JSON_KEY=/absolute/path/to/google-play-service-account.json
```
Optional app targeting:
```bash
GOOGLE_PLAY_PACKAGE_NAME=ai.openclaw.app
```
Android release signing uses the same private `apps-signing` repository and `MATCH_PASSWORD` secret as iOS, but with Android-specific encrypted assets. Pull the shared upload key before release validation:
```bash
pnpm android:release:signing:plan
MATCH_PASSWORD=<signing repo password> pnpm android:release:signing:sync:pull
MATCH_PASSWORD=<signing repo password> pnpm android:release:signing:check
```
The pull command materializes decrypted signing files under `apps/android/build/release-signing/`, which is gitignored. Later Fastlane release commands reload those materialized values and export them to Gradle for the current process.
For the first setup or rotation, provide the Play upload keystore and a local signing properties file, then push encrypted assets to `apps-signing`:
```bash
MATCH_PASSWORD=<signing repo password> \
OPENCLAW_ANDROID_UPLOAD_KEYSTORE=<path-to-upload-keystore.jks> \
OPENCLAW_ANDROID_SIGNING_PROPERTIES=<path-to-android-signing.properties> \
pnpm android:release:signing:sync:push
```
The source signing properties file must contain:
```properties
OPENCLAW_ANDROID_STORE_PASSWORD=<store-password>
OPENCLAW_ANDROID_KEY_ALIAS=<upload-key-alias>
OPENCLAW_ANDROID_KEY_PASSWORD=<key-password>
```
Store the Google Play upload key, not the irreplaceable app signing key, when Play App Signing is enabled.
Validate auth:
```bash
cd apps/android
fastlane android auth_check
```
Archive locally without upload:
```bash
pnpm android:release:archive
```
This command is for local archive validation only. It is not a fallback upload
path after `pnpm android:release:upload` fails.
Generate deterministic Google Play screenshots:
```bash
ANDROID_SCREENSHOT_AVD=OpenClaw_QA_API35 pnpm android:screenshots
```
If exactly one ADB device is already connected, `pnpm android:screenshots`
uses it. With `ANDROID_SCREENSHOT_AVD` or `--avd <name>`, the script can boot a
headless emulator, wait for boot completion, stabilize animation settings,
capture screenshots, and shut down only the emulator it started.
Upload metadata, release notes, and the Play AAB to the configured Google Play track:
```bash
pnpm android:release:upload
```
Direct Fastlane entry point:
```bash
cd apps/android
fastlane android release_upload
```
Use the direct Fastlane entry point only for maintainer debugging when explicitly
requested. Agent-driven releases must use `pnpm android:release:upload` and stop
if it fails.
Release rules:
- `apps/android/version.json` is the pinned Android release version source.
- `apps/android/Config/Version.properties` is generated from that source and read by Gradle.
- `apps/android/CHANGELOG.md` is the Android-only changelog and release-note source.
- `apps/android/fastlane/metadata/android/en-US/release_notes.txt` is generated from that changelog by `pnpm android:version:sync`.
- `apps/android/Config/ReleaseSigning.json` pins the encrypted Android signing assets in the shared signing repo.
- `MATCH_PASSWORD` enables Fastlane to pull encrypted Android signing assets into `apps/android/build/release-signing/` before release validation or archive builds.
- Supported pinned Android versions use CalVer: `YYYY.M.D`.
- `versionCode` uses `YYYYMMDDNN`, where `NN` is a two-digit build number for the pinned version.
- `pnpm android:version:pin -- --from-gateway` promotes the current root gateway version into the pinned Android release version.
- `pnpm android:version:pin -- --version 2026.6.5 --version-code 2026060502` increments another build on the same Android release train.
- `pnpm android:version:sync` updates generated version artifacts.
- `pnpm android:version:check` validates checked-in Android version artifacts.
- `pnpm android:release:preflight` validates Google Play auth, Android release signing, synced versioning, release notes, and prints the package/track/version/versionCode that will be uploaded.
- `pnpm android:release:signing:sync:pull` pulls encrypted Android signing assets from `apps-signing`.
- `pnpm android:release:signing:sync:push` creates or refreshes encrypted Android signing assets in `apps-signing`.
- `pnpm android:screenshots` builds and installs the Play debug app, launches deterministic screenshot scenes, and captures raw PNGs.
- `pnpm android:release:archive` builds the signed Play AAB and third-party APK into `apps/android/build/release-artifacts/`.
- `pnpm android:release:upload` uploads the Play AAB to the configured Google Play track. The default track is `internal`.
- Production promotion remains manual in Google Play Console.
- If `pnpm android:release:upload` fails, agent-driven releases must stop and report the failing step. Do not fall back to `pnpm android:release:archive`, `pnpm android:release:metadata`, direct Fastlane lanes, Gradle release artifacts plus Google Play upload commands, or mobile release ref recording.
Screenshots:
- Android screenshot capture writes raw Play screenshots under `apps/android/fastlane/metadata/android/<locale>/images/phoneScreenshots/`.
- Set `SUPPLY_UPLOAD_SCREENSHOTS=1` to include those screenshots in `fastlane android metadata`.
- Do not commit generated screenshot captures unless they become intentional store metadata assets.

View File

@@ -0,0 +1,3 @@
OpenClaw is now available on Android.
Connect to your OpenClaw Gateway to chat with your assistant, use realtime Talk mode, review approvals, and bring Android device capabilities like camera, location, screen, and notifications into your private automation workflows.

View File

@@ -0,0 +1,3 @@
Adds settings detail panels, refreshes the Android overview controls, and routes exec approvals into the in-app inbox.
Improves chat acknowledgement handling, gateway pairing readiness, microphone foreground-service behavior, and release screenshot reliability.

View File

@@ -0,0 +1,18 @@
OpenClaw is a personal AI assistant you run on your own devices.
Pair this Android app with your OpenClaw Gateway to use your phone as a secure node for chat, voice, approvals, and device-aware automation.
What you can do:
- Pair with your private OpenClaw Gateway by QR code or setup code
- Chat with your assistant from Android
- Use realtime Talk mode and push-to-talk
- Review Gateway action approvals from your phone
- Enable device capabilities such as camera, screen, location, and notifications when you choose
- Receive push wakes and node status updates for connected workflows
OpenClaw is local-first: you control your gateway, keys, configuration, and permissions. Device access is managed by Android permissions and can be enabled only for the capabilities you want to use.
Getting started:
1) Set up your OpenClaw Gateway
2) Open the Android app and pair with your gateway
3) Start using chat, Talk mode, approvals, and automations from your phone

View File

@@ -0,0 +1,5 @@
Improves Android gateway setup with localized onboarding, QR pairing fixes, and support for local mDNS gateway hosts.
Adds clearer recovery guidance for TLS fingerprint timeouts, mobile protocol mismatches, and gateway auth states.
Refreshes native Android localization coverage, including Swedish app naming and localized gateway trust flows.

View File

@@ -0,0 +1 @@
Personal AI on your Android devices

View File

@@ -0,0 +1 @@
OpenClaw