mirror of
https://github.com/hansjone/oclaw.git
synced 2026-10-16 16:40:47 +08:00
重构主控编排与运行时预热链路,统一工作区提示词/专家调度协议并补齐 wiki 记忆注入与写回闭环。
同时收敛启动与运维脚本默认行为(含 wiki worker)、更新 Admin 可观测性与相关测试,降低首轮时延并提高运行稳定性。 Made-with: Cursor
This commit is contained in:
parent
4a23b715a2
commit
dbbe3add6a
14438 changed files with 2693620 additions and 2546 deletions
21
openclaw/apps/ios/fastlane/.env.example
Normal file
21
openclaw/apps/ios/fastlane/.env.example
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
# App Store Connect API key (pick one approach)
|
||||
#
|
||||
# Recommended (use the downloaded .p8 directly):
|
||||
# ASC_KEY_ID=XXXXXXXXXX
|
||||
# ASC_ISSUER_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
# ASC_KEY_PATH=/absolute/path/to/AuthKey_XXXXXXXXXX.p8
|
||||
#
|
||||
# Or (JSON key file):
|
||||
# APP_STORE_CONNECT_API_KEY_PATH=/absolute/path/to/AuthKey_XXXXXX.json
|
||||
#
|
||||
# Or:
|
||||
# ASC_KEY_ID=XXXXXXXXXX
|
||||
# ASC_ISSUER_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
# ASC_KEY_CONTENT=BASE64_P8_CONTENT
|
||||
|
||||
# Code signing
|
||||
# IOS_DEVELOPMENT_TEAM=XXXXXXXXXX
|
||||
|
||||
# Deliver toggles (off by default)
|
||||
# DELIVER_METADATA=1
|
||||
# DELIVER_SCREENSHOTS=1
|
||||
15
openclaw/apps/ios/fastlane/Appfile
Normal file
15
openclaw/apps/ios/fastlane/Appfile
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
app_identifier("ai.openclaw.client")
|
||||
|
||||
# Auth is expected via App Store Connect API key.
|
||||
# Provide either:
|
||||
# - APP_STORE_CONNECT_API_KEY_PATH=/path/to/AuthKey_XXXXXX.p8.json (recommended)
|
||||
# or:
|
||||
# - ASC_KEY_PATH=/path/to/AuthKey_XXXXXX.p8 with ASC_KEY_ID and ASC_ISSUER_ID
|
||||
# - ASC_KEY_ID, ASC_ISSUER_ID, and ASC_KEY_CONTENT (base64 or raw p8 content)
|
||||
# - ASC_KEY_ID and ASC_ISSUER_ID plus Keychain fallback:
|
||||
# ASC_KEYCHAIN_SERVICE (default: openclaw-asc-key)
|
||||
# ASC_KEYCHAIN_ACCOUNT (default: USER/LOGNAME)
|
||||
#
|
||||
# Optional deliver app lookup overrides:
|
||||
# - ASC_APP_IDENTIFIER (bundle ID)
|
||||
# - ASC_APP_ID (numeric App Store Connect app ID)
|
||||
346
openclaw/apps/ios/fastlane/Fastfile
Normal file
346
openclaw/apps/ios/fastlane/Fastfile
Normal file
|
|
@ -0,0 +1,346 @@
|
|||
require "shellwords"
|
||||
require "open3"
|
||||
require "json"
|
||||
|
||||
default_platform(:ios)
|
||||
|
||||
BETA_APP_IDENTIFIER = "ai.openclaw.client"
|
||||
|
||||
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 clear_empty_env_var(key)
|
||||
return unless ENV.key?(key)
|
||||
ENV.delete(key) unless env_present?(ENV[key])
|
||||
end
|
||||
|
||||
def maybe_decode_hex_keychain_secret(value)
|
||||
return value unless env_present?(value)
|
||||
|
||||
candidate = value.strip
|
||||
return candidate unless candidate.match?(/\A[0-9a-fA-F]+\z/) && candidate.length.even?
|
||||
|
||||
begin
|
||||
decoded = [candidate].pack("H*")
|
||||
return candidate unless decoded.valid_encoding?
|
||||
|
||||
# `security find-generic-password -w` can return hex when the stored secret
|
||||
# includes newlines/non-printable bytes (like PEM files).
|
||||
beginPemMarker = %w[BEGIN PRIVATE KEY].join(" ") # pragma: allowlist secret
|
||||
endPemMarker = %w[END PRIVATE KEY].join(" ")
|
||||
if decoded.include?(beginPemMarker) || decoded.include?(endPemMarker)
|
||||
UI.message("Decoded hex-encoded ASC key content from Keychain.")
|
||||
return decoded
|
||||
end
|
||||
rescue StandardError
|
||||
return candidate
|
||||
end
|
||||
|
||||
candidate
|
||||
end
|
||||
|
||||
def read_asc_key_content_from_keychain
|
||||
service = ENV["ASC_KEYCHAIN_SERVICE"]
|
||||
service = "openclaw-asc-key" unless env_present?(service)
|
||||
|
||||
account = ENV["ASC_KEYCHAIN_ACCOUNT"]
|
||||
account = ENV["USER"] unless env_present?(account)
|
||||
account = ENV["LOGNAME"] unless env_present?(account)
|
||||
return nil unless env_present?(account)
|
||||
|
||||
begin
|
||||
stdout, _stderr, status = Open3.capture3(
|
||||
"security",
|
||||
"find-generic-password",
|
||||
"-s",
|
||||
service,
|
||||
"-a",
|
||||
account,
|
||||
"-w"
|
||||
)
|
||||
|
||||
return nil unless status.success?
|
||||
|
||||
key_content = stdout.to_s.strip
|
||||
key_content = maybe_decode_hex_keychain_secret(key_content)
|
||||
return nil unless env_present?(key_content)
|
||||
|
||||
UI.message("Loaded ASC key content from Keychain service '#{service}' (account '#{account}').")
|
||||
key_content
|
||||
rescue Errno::ENOENT
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def repo_root
|
||||
File.expand_path("../../..", __dir__)
|
||||
end
|
||||
|
||||
def ios_root
|
||||
File.expand_path("..", __dir__)
|
||||
end
|
||||
|
||||
def read_ios_version_metadata
|
||||
script_path = File.join(repo_root, "scripts", "ios-version.ts")
|
||||
stdout, stderr, status = Open3.capture3(
|
||||
"node",
|
||||
"--import",
|
||||
"tsx",
|
||||
script_path,
|
||||
"--json",
|
||||
chdir: repo_root
|
||||
)
|
||||
|
||||
unless status.success?
|
||||
detail = stderr.to_s.strip
|
||||
detail = stdout.to_s.strip if detail.empty?
|
||||
UI.user_error!("Failed to read iOS version metadata: #{detail}")
|
||||
end
|
||||
|
||||
parsed = JSON.parse(stdout)
|
||||
version = parsed["canonicalVersion"].to_s.strip
|
||||
short_version = parsed["marketingVersion"].to_s.strip
|
||||
if !env_present?(version) || !env_present?(short_version)
|
||||
UI.user_error!("iOS version helper returned incomplete metadata.")
|
||||
end
|
||||
|
||||
{
|
||||
short_version: short_version,
|
||||
version: version
|
||||
}
|
||||
rescue JSON::ParserError => e
|
||||
UI.user_error!("Invalid JSON from iOS version helper: #{e.message}")
|
||||
end
|
||||
|
||||
def sync_ios_versioning!
|
||||
script_path = File.join(repo_root, "scripts", "ios-sync-versioning.ts")
|
||||
stdout, stderr, status = Open3.capture3(
|
||||
"node",
|
||||
"--import",
|
||||
"tsx",
|
||||
script_path,
|
||||
"--check",
|
||||
chdir: repo_root
|
||||
)
|
||||
return if status.success?
|
||||
|
||||
detail = stderr.to_s.strip
|
||||
detail = stdout.to_s.strip if detail.empty?
|
||||
UI.user_error!("iOS versioning artifacts are stale. Run `pnpm ios:version:sync`.\n#{detail}")
|
||||
end
|
||||
|
||||
def shell_join(parts)
|
||||
Shellwords.join(parts.compact)
|
||||
end
|
||||
|
||||
def resolve_beta_build_number(api_key:, short_version:)
|
||||
explicit = ENV["IOS_BETA_BUILD_NUMBER"]
|
||||
if env_present?(explicit)
|
||||
UI.user_error!("Invalid IOS_BETA_BUILD_NUMBER '#{explicit}'. Expected digits only.") unless explicit.match?(/\A\d+\z/)
|
||||
UI.message("Using explicit iOS beta build number #{explicit}.")
|
||||
return explicit
|
||||
end
|
||||
|
||||
latest_build = latest_testflight_build_number(
|
||||
api_key: api_key,
|
||||
app_identifier: BETA_APP_IDENTIFIER,
|
||||
version: short_version,
|
||||
initial_build_number: 0
|
||||
)
|
||||
next_build = latest_build.to_i + 1
|
||||
UI.message("Resolved iOS beta build number #{next_build} for #{short_version} (latest TestFlight build: #{latest_build}).")
|
||||
next_build.to_s
|
||||
end
|
||||
|
||||
def beta_build_number_needs_asc_auth?
|
||||
explicit = ENV["IOS_BETA_BUILD_NUMBER"]
|
||||
!env_present?(explicit)
|
||||
end
|
||||
|
||||
def prepare_beta_release!(version:, build_number:)
|
||||
script_path = File.join(repo_root, "scripts", "ios-beta-prepare.sh")
|
||||
UI.message("Preparing iOS beta release #{version} (build #{build_number}).")
|
||||
sh(shell_join(["bash", script_path, "--build-number", build_number]))
|
||||
|
||||
beta_xcconfig = File.join(ios_root, "build", "BetaRelease.xcconfig")
|
||||
UI.user_error!("Missing beta xcconfig at #{beta_xcconfig}.") unless File.exist?(beta_xcconfig)
|
||||
|
||||
ENV["XCODE_XCCONFIG_FILE"] = beta_xcconfig
|
||||
beta_xcconfig
|
||||
end
|
||||
|
||||
def build_beta_release(context)
|
||||
version = context[:version]
|
||||
output_directory = File.join("build", "beta")
|
||||
archive_path = File.join(output_directory, "OpenClaw-#{version}.xcarchive")
|
||||
|
||||
build_app(
|
||||
project: "OpenClaw.xcodeproj",
|
||||
scheme: "OpenClaw",
|
||||
configuration: "Release",
|
||||
export_method: "app-store",
|
||||
clean: true,
|
||||
skip_profile_detection: true,
|
||||
build_path: "build",
|
||||
archive_path: archive_path,
|
||||
output_directory: output_directory,
|
||||
output_name: "OpenClaw-#{version}.ipa",
|
||||
xcargs: "-allowProvisioningUpdates",
|
||||
export_xcargs: "-allowProvisioningUpdates",
|
||||
export_options: {
|
||||
signingStyle: "automatic"
|
||||
}
|
||||
)
|
||||
|
||||
{
|
||||
archive_path: archive_path,
|
||||
build_number: context[:build_number],
|
||||
ipa_path: lane_context[SharedValues::IPA_OUTPUT_PATH],
|
||||
short_version: context[:short_version],
|
||||
version: version
|
||||
}
|
||||
end
|
||||
|
||||
platform :ios do
|
||||
private_lane :asc_api_key do
|
||||
load_env_file(File.join(__dir__, ".env"))
|
||||
clear_empty_env_var("APP_STORE_CONNECT_API_KEY_PATH")
|
||||
clear_empty_env_var("ASC_KEY_PATH")
|
||||
clear_empty_env_var("ASC_KEY_CONTENT")
|
||||
|
||||
api_key = nil
|
||||
|
||||
key_path = ENV["APP_STORE_CONNECT_API_KEY_PATH"]
|
||||
if env_present?(key_path)
|
||||
api_key = app_store_connect_api_key(path: key_path)
|
||||
else
|
||||
p8_path = ENV["ASC_KEY_PATH"]
|
||||
if env_present?(p8_path)
|
||||
key_id = ENV["ASC_KEY_ID"]
|
||||
issuer_id = ENV["ASC_ISSUER_ID"]
|
||||
UI.user_error!("Missing ASC_KEY_ID or ASC_ISSUER_ID for ASC_KEY_PATH auth.") if [key_id, issuer_id].any? { |v| !env_present?(v) }
|
||||
|
||||
api_key = app_store_connect_api_key(
|
||||
key_id: key_id,
|
||||
issuer_id: issuer_id,
|
||||
key_filepath: p8_path
|
||||
)
|
||||
else
|
||||
key_id = ENV["ASC_KEY_ID"]
|
||||
issuer_id = ENV["ASC_ISSUER_ID"]
|
||||
key_content = ENV["ASC_KEY_CONTENT"]
|
||||
key_content = read_asc_key_content_from_keychain unless env_present?(key_content)
|
||||
|
||||
UI.user_error!(
|
||||
"Missing App Store Connect API key. Set APP_STORE_CONNECT_API_KEY_PATH (json), ASC_KEY_PATH (p8), or ASC_KEY_ID/ASC_ISSUER_ID with ASC_KEY_CONTENT (or Keychain via ASC_KEYCHAIN_SERVICE/ASC_KEYCHAIN_ACCOUNT)."
|
||||
) if [key_id, issuer_id, key_content].any? { |v| !env_present?(v) }
|
||||
|
||||
is_base64 = key_content.include?("BEGIN PRIVATE KEY") ? false : true
|
||||
|
||||
api_key = app_store_connect_api_key(
|
||||
key_id: key_id,
|
||||
issuer_id: issuer_id,
|
||||
key_content: key_content,
|
||||
is_key_content_base64: is_base64
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
api_key
|
||||
end
|
||||
|
||||
private_lane :prepare_beta_context do |options|
|
||||
require_api_key = options[:require_api_key] == true
|
||||
needs_api_key = require_api_key || beta_build_number_needs_asc_auth?
|
||||
api_key = needs_api_key ? asc_api_key : nil
|
||||
sync_ios_versioning!
|
||||
version_metadata = read_ios_version_metadata
|
||||
version = version_metadata[:version]
|
||||
short_version = version_metadata[:short_version]
|
||||
build_number = resolve_beta_build_number(api_key: api_key, short_version: short_version)
|
||||
beta_xcconfig = prepare_beta_release!(version: version, build_number: build_number)
|
||||
|
||||
{
|
||||
api_key: api_key,
|
||||
beta_xcconfig: beta_xcconfig,
|
||||
build_number: build_number,
|
||||
short_version: short_version,
|
||||
version: version
|
||||
}
|
||||
end
|
||||
|
||||
desc "Build a beta archive locally without uploading"
|
||||
lane :beta_archive do
|
||||
context = prepare_beta_context(require_api_key: false)
|
||||
build = build_beta_release(context)
|
||||
UI.success("Built iOS beta archive: version=#{build[:version]} short=#{build[:short_version]} build=#{build[:build_number]}")
|
||||
build
|
||||
ensure
|
||||
ENV.delete("XCODE_XCCONFIG_FILE")
|
||||
end
|
||||
|
||||
desc "Build + upload a beta to TestFlight"
|
||||
lane :beta do
|
||||
context = prepare_beta_context(require_api_key: true)
|
||||
build = build_beta_release(context)
|
||||
|
||||
upload_to_testflight(
|
||||
api_key: context[:api_key],
|
||||
ipa: build[:ipa_path],
|
||||
skip_waiting_for_build_processing: true,
|
||||
uses_non_exempt_encryption: false
|
||||
)
|
||||
|
||||
UI.success("Uploaded iOS beta: version=#{build[:version]} short=#{build[:short_version]} build=#{build[:build_number]}")
|
||||
ensure
|
||||
ENV.delete("XCODE_XCCONFIG_FILE")
|
||||
end
|
||||
|
||||
desc "Upload App Store metadata (and optionally screenshots)"
|
||||
lane :metadata do
|
||||
sync_ios_versioning!
|
||||
api_key = asc_api_key
|
||||
clear_empty_env_var("APP_STORE_CONNECT_API_KEY_PATH")
|
||||
app_identifier = ENV["ASC_APP_IDENTIFIER"]
|
||||
app_id = ENV["ASC_APP_ID"]
|
||||
app_identifier = nil unless env_present?(app_identifier)
|
||||
app_id = nil unless env_present?(app_id)
|
||||
|
||||
deliver_options = {
|
||||
api_key: api_key,
|
||||
force: true,
|
||||
skip_screenshots: ENV["DELIVER_SCREENSHOTS"] != "1",
|
||||
skip_metadata: ENV["DELIVER_METADATA"] != "1",
|
||||
run_precheck_before_submit: false
|
||||
}
|
||||
deliver_options[:app_identifier] = app_identifier if app_identifier
|
||||
if app_id && app_identifier.nil?
|
||||
# `deliver` prefers app_identifier from Appfile unless explicitly blanked.
|
||||
deliver_options[:app_identifier] = ""
|
||||
deliver_options[:app] = app_id
|
||||
end
|
||||
|
||||
deliver(**deliver_options)
|
||||
end
|
||||
|
||||
desc "Validate App Store Connect API auth"
|
||||
lane :auth_check do
|
||||
asc_api_key
|
||||
UI.success("App Store Connect API auth loaded successfully.")
|
||||
end
|
||||
end
|
||||
149
openclaw/apps/ios/fastlane/SETUP.md
Normal file
149
openclaw/apps/ios/fastlane/SETUP.md
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
# fastlane setup (OpenClaw iOS)
|
||||
|
||||
Install:
|
||||
|
||||
```bash
|
||||
brew install fastlane
|
||||
```
|
||||
|
||||
Create an App Store Connect API key:
|
||||
|
||||
- App Store Connect → Users and Access → Keys → App Store Connect API → Generate API Key
|
||||
- Download the `.p8`, note the **Issuer ID** and **Key ID**
|
||||
|
||||
Recommended (macOS): store the private key in Keychain and write non-secret vars:
|
||||
|
||||
```bash
|
||||
scripts/ios-asc-keychain-setup.sh \
|
||||
--key-path /absolute/path/to/AuthKey_XXXXXXXXXX.p8 \
|
||||
--issuer-id YOUR_ISSUER_ID \
|
||||
--write-env
|
||||
```
|
||||
|
||||
This writes these auth variables in `apps/ios/fastlane/.env`:
|
||||
|
||||
```bash
|
||||
ASC_KEY_ID=YOUR_KEY_ID
|
||||
ASC_ISSUER_ID=YOUR_ISSUER_ID
|
||||
ASC_KEYCHAIN_SERVICE=openclaw-asc-key
|
||||
ASC_KEYCHAIN_ACCOUNT=YOUR_MAC_USERNAME
|
||||
```
|
||||
|
||||
Important: `apps/ios/fastlane/.env` is only for Fastlane/App Store Connect auth and optional beta-archive settings. It does **not** configure gateway-side direct APNs push delivery for local iOS builds.
|
||||
|
||||
Optional app targeting variables (helpful if Fastlane cannot auto-resolve app by bundle):
|
||||
|
||||
```bash
|
||||
ASC_APP_IDENTIFIER=ai.openclaw.client
|
||||
# or
|
||||
ASC_APP_ID=YOUR_APP_STORE_CONNECT_APP_ID
|
||||
```
|
||||
|
||||
File-based fallback (CI/non-macOS):
|
||||
|
||||
```bash
|
||||
ASC_KEY_ID=YOUR_KEY_ID
|
||||
ASC_ISSUER_ID=YOUR_ISSUER_ID
|
||||
ASC_KEY_PATH=/absolute/path/to/AuthKey_XXXXXXXXXX.p8
|
||||
```
|
||||
|
||||
Code signing variable (optional in `.env`):
|
||||
|
||||
```bash
|
||||
IOS_DEVELOPMENT_TEAM=YOUR_TEAM_ID
|
||||
```
|
||||
|
||||
Tip: run `scripts/ios-team-id.sh` from repo root to print a Team ID for `.env`. The helper prefers the canonical OpenClaw team (`Y5PE65HELJ`) when present locally; otherwise it prefers the first non-personal team from your Xcode account (then personal team if needed). Fastlane uses this helper automatically if `IOS_DEVELOPMENT_TEAM` is missing.
|
||||
|
||||
For local/manual iOS builds that stay on direct APNs, configure the gateway host separately with `OPENCLAW_APNS_TEAM_ID`, `OPENCLAW_APNS_KEY_ID`, and either `OPENCLAW_APNS_PRIVATE_KEY_P8` or `OPENCLAW_APNS_PRIVATE_KEY_PATH`. Those gateway runtime env vars are separate from Fastlane's `.env`.
|
||||
|
||||
Validate auth:
|
||||
|
||||
```bash
|
||||
cd apps/ios
|
||||
fastlane ios auth_check
|
||||
```
|
||||
|
||||
ASC auth is only required when:
|
||||
|
||||
- uploading to TestFlight
|
||||
- auto-resolving the next build number from App Store Connect
|
||||
|
||||
If you pass `--build-number` to `pnpm ios:beta:archive`, the local archive path does not need ASC auth.
|
||||
|
||||
Archive locally without upload:
|
||||
|
||||
```bash
|
||||
pnpm ios:beta:archive
|
||||
```
|
||||
|
||||
Upload to TestFlight:
|
||||
|
||||
```bash
|
||||
pnpm ios:beta
|
||||
```
|
||||
|
||||
Direct Fastlane entry point:
|
||||
|
||||
```bash
|
||||
cd apps/ios
|
||||
fastlane ios beta
|
||||
```
|
||||
|
||||
Maintainer recovery path for a fresh clone on the same Mac:
|
||||
|
||||
1. Reuse the existing Keychain-backed ASC key on that machine.
|
||||
2. Restore or recreate `apps/ios/fastlane/.env` so it contains the non-secret variables:
|
||||
|
||||
```bash
|
||||
ASC_KEY_ID=YOUR_KEY_ID
|
||||
ASC_ISSUER_ID=YOUR_ISSUER_ID
|
||||
ASC_KEYCHAIN_SERVICE=openclaw-asc-key
|
||||
ASC_KEYCHAIN_ACCOUNT=YOUR_MAC_USERNAME
|
||||
```
|
||||
|
||||
3. Re-run auth validation:
|
||||
|
||||
```bash
|
||||
cd apps/ios
|
||||
fastlane ios auth_check
|
||||
```
|
||||
|
||||
4. If you are starting a brand-new production release train, pin iOS to the current gateway version:
|
||||
|
||||
```bash
|
||||
pnpm ios:version:pin -- --from-gateway
|
||||
```
|
||||
|
||||
5. Set the official/TestFlight relay URL before release:
|
||||
|
||||
```bash
|
||||
export OPENCLAW_PUSH_RELAY_BASE_URL=https://relay.example.com
|
||||
```
|
||||
|
||||
6. Upload:
|
||||
|
||||
```bash
|
||||
pnpm ios:beta
|
||||
```
|
||||
|
||||
Quick verification after upload:
|
||||
|
||||
- confirm `apps/ios/build/beta/OpenClaw-<version>.ipa` exists
|
||||
- confirm Fastlane prints `Uploaded iOS beta: version=<version> short=<short> build=<build>`
|
||||
- remember that TestFlight processing can take a few minutes after the upload succeeds
|
||||
|
||||
Versioning rules:
|
||||
|
||||
- `apps/ios/version.json` is the pinned iOS release version source
|
||||
- `apps/ios/CHANGELOG.md` is the iOS-only changelog and release-note source
|
||||
- Supported pinned iOS versions use CalVer: `YYYY.M.D`
|
||||
- `pnpm ios:version:pin -- --from-gateway` promotes the current root gateway version into the pinned iOS release version
|
||||
- Fastlane uses the pinned iOS version only; changing `package.json.version` alone does not change the iOS app version
|
||||
- Fastlane sets `CFBundleShortVersionString` to the pinned iOS version, for example `2026.4.10`
|
||||
- Fastlane resolves `CFBundleVersion` as the next integer TestFlight build number for that short version
|
||||
- Run `pnpm ios:version:sync` after changing `apps/ios/version.json` or `apps/ios/CHANGELOG.md`
|
||||
- `pnpm ios:version:check` validates that checked-in iOS version artifacts are in sync
|
||||
- The beta flow regenerates `apps/ios/OpenClaw.xcodeproj` from `apps/ios/project.yml` before archiving
|
||||
- Local beta signing uses a temporary generated xcconfig and leaves local development signing overrides untouched
|
||||
- See `apps/ios/VERSIONING.md` for the detailed workflow
|
||||
50
openclaw/apps/ios/fastlane/metadata/README.md
Normal file
50
openclaw/apps/ios/fastlane/metadata/README.md
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
# App Store metadata (Fastlane deliver)
|
||||
|
||||
This directory is used by `fastlane deliver` for App Store Connect text metadata.
|
||||
|
||||
## Upload metadata only
|
||||
|
||||
```bash
|
||||
cd apps/ios
|
||||
ASC_APP_ID=YOUR_APP_STORE_CONNECT_APP_ID \
|
||||
DELIVER_METADATA=1 fastlane ios metadata
|
||||
```
|
||||
|
||||
## Optional: include screenshots
|
||||
|
||||
```bash
|
||||
cd apps/ios
|
||||
DELIVER_METADATA=1 DELIVER_SCREENSHOTS=1 fastlane ios metadata
|
||||
```
|
||||
|
||||
## Auth
|
||||
|
||||
The `ios metadata` lane uses App Store Connect API key auth from `apps/ios/fastlane/.env`:
|
||||
|
||||
- Keychain-backed (recommended on macOS):
|
||||
- `ASC_KEY_ID`
|
||||
- `ASC_ISSUER_ID`
|
||||
- `ASC_KEYCHAIN_SERVICE` (default: `openclaw-asc-key`)
|
||||
- `ASC_KEYCHAIN_ACCOUNT` (default: current user)
|
||||
- File/path fallback:
|
||||
- `ASC_KEY_ID`
|
||||
- `ASC_ISSUER_ID`
|
||||
- `ASC_KEY_PATH`
|
||||
|
||||
Or set `APP_STORE_CONNECT_API_KEY_PATH`.
|
||||
|
||||
## Notes
|
||||
|
||||
- Locale files live under `metadata/en-US/`.
|
||||
- `release_notes.txt` is generated from `apps/ios/CHANGELOG.md`; after changelog updates, run `pnpm ios:version:sync`.
|
||||
- Release notes resolve from `## <pinned iOS version>` first, then fall back to `## Unreleased` while a TestFlight train is still in progress.
|
||||
- When starting a new production release train, pin the iOS version first with `pnpm ios:version:pin -- --from-gateway`.
|
||||
- `privacy_url.txt` is set to `https://openclaw.ai/privacy`.
|
||||
- If app lookup fails in `deliver`, set one of:
|
||||
- `ASC_APP_IDENTIFIER` (bundle ID)
|
||||
- `ASC_APP_ID` (numeric App Store Connect app ID, e.g. from `/apps/<id>/...` URL)
|
||||
- For first app versions, include review contact files under `metadata/review_information/`:
|
||||
- `first_name.txt`
|
||||
- `last_name.txt`
|
||||
- `email_address.txt`
|
||||
- `phone_number.txt` (E.164-ish, e.g. `+1 415 555 0100`)
|
||||
18
openclaw/apps/ios/fastlane/metadata/en-US/description.txt
Normal file
18
openclaw/apps/ios/fastlane/metadata/en-US/description.txt
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
OpenClaw is a personal AI assistant you run on your own devices.
|
||||
|
||||
Pair this iPhone app with your OpenClaw Gateway to connect your phone as a secure node for voice, camera, and device automation.
|
||||
|
||||
What you can do:
|
||||
- Chat with your assistant from iPhone
|
||||
- Use voice wake and push-to-talk
|
||||
- Capture photos and short clips on request
|
||||
- Record screen snippets for troubleshooting and workflows
|
||||
- Share text, links, and media directly from iOS into OpenClaw
|
||||
- Run location-aware and device-aware automations
|
||||
|
||||
OpenClaw is local-first: you control your gateway, keys, and configuration.
|
||||
|
||||
Getting started:
|
||||
1) Set up your OpenClaw Gateway
|
||||
2) Open the iOS app and pair with your gateway
|
||||
3) Start using commands and automations from your phone
|
||||
1
openclaw/apps/ios/fastlane/metadata/en-US/keywords.txt
Normal file
1
openclaw/apps/ios/fastlane/metadata/en-US/keywords.txt
Normal file
|
|
@ -0,0 +1 @@
|
|||
openclaw,ai assistant,local ai,voice assistant,automation,gateway,chat,agent,node
|
||||
|
|
@ -0,0 +1 @@
|
|||
https://openclaw.ai
|
||||
1
openclaw/apps/ios/fastlane/metadata/en-US/name.txt
Normal file
1
openclaw/apps/ios/fastlane/metadata/en-US/name.txt
Normal file
|
|
@ -0,0 +1 @@
|
|||
OpenClaw - iOS Client
|
||||
|
|
@ -0,0 +1 @@
|
|||
https://openclaw.ai/privacy
|
||||
|
|
@ -0,0 +1 @@
|
|||
Run OpenClaw from your iPhone: pair with your own gateway, trigger automations, and use voice, camera, and share actions.
|
||||
|
|
@ -0,0 +1 @@
|
|||
Maintenance update for the current OpenClaw release.
|
||||
1
openclaw/apps/ios/fastlane/metadata/en-US/subtitle.txt
Normal file
1
openclaw/apps/ios/fastlane/metadata/en-US/subtitle.txt
Normal file
|
|
@ -0,0 +1 @@
|
|||
Personal AI on your devices
|
||||
|
|
@ -0,0 +1 @@
|
|||
https://docs.openclaw.ai/platforms/ios
|
||||
|
|
@ -0,0 +1 @@
|
|||
support@openclaw.ai
|
||||
|
|
@ -0,0 +1 @@
|
|||
OpenClaw
|
||||
|
|
@ -0,0 +1 @@
|
|||
Team
|
||||
|
|
@ -0,0 +1 @@
|
|||
OpenClaw iOS client for gateway-connected workflows. Reviewers can follow the standard onboarding and pairing flow in-app.
|
||||
|
|
@ -0,0 +1 @@
|
|||
+1 415 555 0100
|
||||
Loading…
Add table
Add a link
Reference in a new issue