Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 50 additions & 40 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,65 +57,75 @@ jobs:
run: cd Packages/MoriIPC && swift run MoriIPCTests

ios-build:
needs: ghosttykit
runs-on: macos-26
timeout-minutes: 20
timeout-minutes: 30
env:
DERIVED_DATA: .derived-data-ios
steps:
- name: Checkout
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
submodules: recursive

- name: Download Mori-built GhosttyKit
uses: actions/download-artifact@v7
with:
name: GhosttyKit
path: Frameworks

- name: Verify universal GhosttyKit
run: bash scripts/verify-ghosttykit.sh

- name: Cache SPM packages
uses: actions/cache@v5
with:
path: MoriRemote/.derived-data-sim/SourcePackages
key: spm-ios-${{ hashFiles('MoriRemote/MoriRemote.xcodeproj/project.pbxproj', '**/Package.resolved') }}
path: ${{ env.DERIVED_DATA }}/SourcePackages
key: spm-ios-${{ hashFiles('MoriRemote/project.yml', '**/Package.resolved') }}
restore-keys: spm-ios-

- name: Install tools
run: |
brew install xcodegen xcbeautify

- name: Stub GhosttyKit for SPM resolution
run: |
mkdir -p Frameworks/GhosttyKit.xcframework/macos-arm64_x86_64/GhosttyKit.framework/Headers
touch Frameworks/GhosttyKit.xcframework/macos-arm64_x86_64/GhosttyKit.framework/GhosttyKit
cat > Frameworks/GhosttyKit.xcframework/Info.plist << 'PLIST'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>AvailableLibraries</key>
<array>
<dict>
<key>LibraryIdentifier</key>
<string>macos-arm64_x86_64</string>
<key>LibraryPath</key>
<string>GhosttyKit.framework</string>
<key>SupportedArchitectures</key>
<array><string>arm64</string><string>x86_64</string></array>
<key>SupportedPlatform</key>
<string>macos</string>
</dict>
</array>
<key>CFBundlePackageType</key>
<string>XFWK</string>
<key>XCFrameworkFormatVersion</key>
<string>1.0</string>
</dict>
</plist>
PLIST
run: brew install xcodegen xcbeautify

- name: Generate Xcode project
run: cd MoriRemote && xcodegen generate

- name: Build for iOS Simulator
- name: Run MoriRemote Swift Testing suite
run: |
set -euo pipefail
SIMULATOR="$(xcrun simctl list devices available | grep 'iPhone' | grep -oE '[A-F0-9-]{36}' | head -1)"
xcodebuild \
-project MoriRemote/MoriRemote.xcodeproj \
-scheme MoriRemote \
-destination 'generic/platform=iOS Simulator' \
-derivedDataPath .derived-data-ios \
build 2>&1 | xcbeautify
-destination "platform=iOS Simulator,id=$SIMULATOR" \
-derivedDataPath "$DERIVED_DATA" \
test 2>&1 | xcbeautify

- name: Build iPhone and iPad Release simulator products
run: |
set -euo pipefail
for family in iPhone iPad; do
SIMULATOR="$(xcrun simctl list devices available | grep "$family" | grep -oE '[A-F0-9-]{36}' | head -1)"
xcodebuild \
-project MoriRemote/MoriRemote.xcodeproj \
-scheme MoriRemote \
-configuration Release \
-destination "platform=iOS Simulator,id=$SIMULATOR" \
-derivedDataPath "$DERIVED_DATA" \
build 2>&1 | xcbeautify
done

- name: Smoke library and deterministic Ghostty terminal
run: MORI_IOS_SMOKE_OUTPUT="$RUNNER_TEMP/moriremote-smoke" bash scripts/smoke-moriremote-simulator.sh

- name: Upload simulator smoke evidence
if: always()
uses: actions/upload-artifact@v7
with:
name: MoriRemote-simulator-smoke
path: ${{ runner.temp }}/moriremote-smoke
if-no-files-found: warn
retention-days: 14

bundle:
needs: [ghosttykit, build-and-test]
Expand Down
81 changes: 35 additions & 46 deletions .github/workflows/release-ios.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ on:
workflow_dispatch:
inputs:
version:
description: "Version (e.g. 0.1.0)"
description: "Version (must be 0.3.5)"
required: true
build_number:
description: "Optional build number override (defaults to UTC timestamp, e.g. 202604071105)"
Expand All @@ -21,79 +21,70 @@ concurrency:
cancel-in-progress: false

jobs:
ghosttykit:
uses: ./.github/workflows/build-ghosttykit.yml

release-ios:
needs: ghosttykit
runs-on: macos-26
timeout-minutes: 30
env:
DERIVED_DATA: .derived-data-ios
steps:
- name: Checkout
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
submodules: recursive

- name: Resolve version
- name: Resolve fixed marketing version and build number
id: version
env:
EVENT_NAME: ${{ github.event_name }}
VERSION_INPUT: ${{ inputs.version }}
BUILD_NUMBER_INPUT: ${{ inputs.build_number }}
run: |
set -euo pipefail
DEFAULT_BUILD_NUMBER="$(date -u +%Y%m%d%H%M)"
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
VERSION="${{ github.event.inputs.version }}"
BUILD_NUMBER="${{ github.event.inputs.build_number }}"
if [[ "$EVENT_NAME" == "workflow_dispatch" ]]; then
VERSION="$VERSION_INPUT"
BUILD_NUMBER="$BUILD_NUMBER_INPUT"
if [[ -z "$BUILD_NUMBER" ]]; then
BUILD_NUMBER="$DEFAULT_BUILD_NUMBER"
fi
else
VERSION="${GITHUB_REF_NAME#ios-v}"
BUILD_NUMBER="$DEFAULT_BUILD_NUMBER"
fi
[[ "$VERSION" == "0.3.5" ]] || { echo "MoriRemote TestFlight marketing version is pinned to 0.3.5." >&2; exit 1; }
[[ "$BUILD_NUMBER" =~ ^[0-9]+$ ]] || { echo "Build number must contain only digits." >&2; exit 1; }
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
echo "build_number=${BUILD_NUMBER}" >> "$GITHUB_OUTPUT"
echo "📦 Version: ${VERSION} (${BUILD_NUMBER})"

- name: Download Mori-built GhosttyKit
uses: actions/download-artifact@v7
with:
name: GhosttyKit
path: Frameworks

- name: Verify universal GhosttyKit
run: bash scripts/verify-ghosttykit.sh

- name: Install tools
run: |
brew install xcodegen xcbeautify
run: brew install xcodegen xcbeautify

- name: Cache SPM packages
uses: actions/cache@v5
with:
path: ${{ env.DERIVED_DATA }}/SourcePackages
key: spm-ios-${{ hashFiles('MoriRemote/MoriRemote.xcodeproj/project.pbxproj', '**/Package.resolved') }}
key: spm-ios-${{ hashFiles('MoriRemote/project.yml', '**/Package.resolved') }}
restore-keys: spm-ios-

- name: Generate Xcode project
env:
DEVELOPMENT_TEAM: ${{ secrets.APPLE_TEAM_ID }}
run: cd MoriRemote && xcodegen generate

- name: Stub GhosttyKit for SPM resolution
run: |
mkdir -p Frameworks/GhosttyKit.xcframework/macos-arm64_x86_64/GhosttyKit.framework/Headers
touch Frameworks/GhosttyKit.xcframework/macos-arm64_x86_64/GhosttyKit.framework/GhosttyKit
cat > Frameworks/GhosttyKit.xcframework/Info.plist << 'PLIST'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>AvailableLibraries</key>
<array>
<dict>
<key>LibraryIdentifier</key>
<string>macos-arm64_x86_64</string>
<key>LibraryPath</key>
<string>GhosttyKit.framework</string>
<key>SupportedArchitectures</key>
<array><string>arm64</string><string>x86_64</string></array>
<key>SupportedPlatform</key>
<string>macos</string>
</dict>
</array>
<key>CFBundlePackageType</key>
<string>XFWK</string>
<key>XCFrameworkFormatVersion</key>
<string>1.0</string>
</dict>
</plist>
PLIST

- name: Import signing certificate
env:
APPLE_CERTIFICATE_P12: ${{ secrets.IOS_CERTIFICATE_P12 }}
Expand Down Expand Up @@ -146,7 +137,8 @@ jobs:
MARKETING_VERSION="$VERSION" \
CURRENT_PROJECT_VERSION="$BUILD_NUMBER" \
2>&1 | xcbeautify
echo "✅ Archive created"
bash scripts/verify-moriremote-archive.sh "$DERIVED_DATA/MoriRemote.xcarchive" "$VERSION" "$BUILD_NUMBER"
echo "✅ Archive created and inspected"

- name: Export IPA
run: |
Expand All @@ -164,11 +156,8 @@ jobs:
APPLE_APP_PASSWORD: ${{ secrets.APPLE_APP_PASSWORD }}
run: |
set -euo pipefail
IPA_PATH=$(find "$DERIVED_DATA/export" -name "*.ipa" | head -1)
if [ -z "$IPA_PATH" ]; then
echo "❌ No IPA found"
exit 1
fi
IPA_PATH=$(find "$DERIVED_DATA/export" -name "*.ipa" -print -quit)
[[ -n "$IPA_PATH" ]] || { echo "❌ No IPA found"; exit 1; }
xcrun altool --upload-app \
--type ios \
--file "$IPA_PATH" \
Expand All @@ -177,7 +166,7 @@ jobs:
echo "✅ Uploaded to TestFlight"

- name: Upload IPA artifact
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@v7
with:
name: MoriRemote-${{ steps.version.outputs.version }}.ipa
path: ${{ env.DERIVED_DATA }}/export/*.ipa
Expand Down
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ mise run test:ui # MoriUI tests only
mise run clean # Remove .build and .derived-data
```

Tests are executable targets (not XCTest), run via `swift run <TestTarget>` from each package directory.
Tests are executable targets (not XCTest), run via `swift run <TestTarget>` from each package directory. MoriRemote is the narrow exception: `mise run ios:test` runs its Swift Testing suite and bootstraps the universal GhosttyKit artifact with `mise run build:ghostty-universal`. `mise run ios:run` / `mise run ios:smoke` build, install, verify process liveness and crash diagnostics, and save library/terminal screenshots; a returned simulator PID alone is not success. Before an iOS release, `bash scripts/verify-moriremote-archive.sh <archive> 0.3.5` verifies the archive contract.

## Pre-Push Verification

Expand Down
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### ✨ Features

- **iOS (MoriRemote)**: Rebuilt the remote companion around a secure, pane-native tmux control runtime. It supports saved workspaces, password or OpenSSH private-key authentication, explicit host-key trust, local terminal history/selection, agent status, and adaptive iPhone/iPad presentation without taking over another attached tmux client's selection or size.

### 🐛 Bug Fixes

- **iOS (MoriRemote)**: Fixed SSH tmux connections remaining on “Waiting for the active tmux pane” even though the remote control client had attached.
- **iOS (MoriRemote)**: Hardened credentials to device-bound, unlocked-only Keychain storage; fenced Ghostty shutdown behind terminal-surface teardown; defer reconnects while backgrounded; and release dormant runtimes first under memory pressure.

### 🔧 CI/CD

- **GhosttyKit**: Build one universal macOS + iOS framework from the pinned remux Ghostty source and verify its provenance, platform slices, build mode, and tmux ABI before reuse.
- **iOS (MoriRemote)**: CI consumes that same-run verified universal framework for app tests, simulator smoke, and archives; TestFlight marketing version remains pinned to `0.3.5`.



## [0.7.0] - 2026-07-31

Expand Down
12 changes: 12 additions & 0 deletions CHANGELOG.zh-Hans.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,21 @@

## [Unreleased]

### ✨ 新功能

- **iOS(MoriRemote)**:远程伴侣已重构为安全、以 pane 为原生单位的 tmux 控制运行时。支持保存工作区、密码或 OpenSSH 私钥认证、显式主机密钥信任、本地终端历史/选择、agent 状态,以及自适应 iPhone/iPad 界面;不会接管其他已连接 tmux 客户端的选择或尺寸。

### 🐛 问题修复

- **iOS(MoriRemote)**:修复远端 tmux 控制客户端已经连接,但界面仍一直停在“正在等待活动的 tmux pane”的问题。
- **iOS(MoriRemote)**:凭证改为仅限本设备、仅在解锁时可用的 Keychain 存储;Ghostty 必须在终端 surface 拆除后才释放;后台期间延后重连;低内存时优先释放非活动运行时。

### 🔧 CI/CD

- **GhosttyKit**:从固定的 remux Ghostty 源码构建一份通用 macOS + iOS framework,并在复用前校验其来源、平台 slice、构建模式和 tmux ABI。
- **iOS(MoriRemote)**:CI 在应用测试、模拟器 smoke 和 archive 中消费同一次运行内已验证的通用 framework;TestFlight 营销版本仍固定为 `0.3.5`。



## [0.7.0] - 2026-07-31

Expand Down
Loading
Loading