refactor: consolidate CLI binaries and streamline CI workflows (#44)

* chore: update dependencies and remove unused packages

- Removed several unused dependencies from package.json, including `@tauri-apps/plugin-shell`, `@types/react-router-dom`, `immer`, `qrcode.react`, and `@testing-library/user-event`.
- Updated the Vite configuration to exclude `telegram` from the optimizeDeps include list.
- Cleaned up yarn.lock by removing references to deleted packages.
- Deleted outdated GitHub Actions workflows for macOS ARM64 build, package and publish, and update changelog.

* refactor: consolidate CLI binaries and update configurations

- Renamed the main binary from `openhuman-core` to `openhuman` for consistency across the project.
- Removed the `openhuman-cli` and `openhuman-core` binaries, consolidating functionality into the new `openhuman` binary.
- Updated build configurations in GitHub Actions and Tauri to reflect the new binary name and paths.
- Adjusted the Tailwind and Vite configurations to point to the correct HTML and source directories.
- Introduced a new method in the Rust core server for handling JSON-RPC calls, enhancing the API structure.

* chore: update subproject commit reference in skills

* chore: update Rust test workflow and improve CLI command formatting

- Modified the GitHub Actions workflow to include a new job for running Rust tests in the workspace.
- Updated the `test:rust` command in `package.json` to run tests for the entire workspace.
- Refactored CLI command definitions in `openhuman.rs` for improved readability by consolidating struct fields into single lines.

* chore: enhance E2E testing scripts and workflows

- Updated `.prettierignore` to exclude the `rust-core` directory.
- Added new E2E testing commands in `package.json` for running all flows and improved formatting commands to include `cargo fmt`.
- Enhanced GitHub Actions workflows to streamline E2E testing, including new scripts for running individual specs and all flows sequentially.
- Refactored existing E2E scripts to utilize a common function for running tests, improving maintainability and readability.
- Introduced a new script for resolving Node.js and Appium dependencies, ensuring compatibility for E2E tests.
- Cleaned up and simplified existing E2E scripts by removing redundant code and consolidating functionality.

* chore: update GitHub Actions workflows for Tauri build process

- Modified build and release workflows to reference the correct target directory for Rust binaries, ensuring compatibility with the Cargo workspace structure.
- Adjusted caching configuration in the test workflow for consistency and improved readability.
- Standardized environment variable formatting in the test workflow to enhance clarity.

* fix: standardize string formatting in GitHub Actions workflows

- Updated the test workflow to use single quotes for cache configuration and environment variables, enhancing consistency across the file.
This commit is contained in:
Steven Enamakel
2026-03-27 13:58:29 -07:00
committed by GitHub
parent 37991fc567
commit 5ecc7cbc75
35 changed files with 692 additions and 2196 deletions
+4 -3
View File
@@ -73,13 +73,14 @@ jobs:
run: cd skills && yarn install --frozen-lockfile
- name: Build sidecar core binary
run: cargo build --manifest-path rust-core/Cargo.toml --release --target x86_64-unknown-linux-gnu --bin openhuman-core
run: cargo build --manifest-path rust-core/Cargo.toml --release --target x86_64-unknown-linux-gnu --bin openhuman
- name: Stage sidecar for Tauri bundler
run: |
mkdir -p src-tauri/binaries
cp rust-core/target/x86_64-unknown-linux-gnu/release/openhuman-core src-tauri/binaries/openhuman-core-x86_64-unknown-linux-gnu
chmod +x src-tauri/binaries/openhuman-core-x86_64-unknown-linux-gnu
# Workspace root target/ (not rust-core/target/) — see root Cargo.toml [workspace]
cp target/x86_64-unknown-linux-gnu/release/openhuman src-tauri/binaries/openhuman-x86_64-unknown-linux-gnu
chmod +x src-tauri/binaries/openhuman-x86_64-unknown-linux-gnu
- name: Build Tauri app
run: |
-125
View File
@@ -1,125 +0,0 @@
# Standalone macOS Apple Silicon (aarch64) Tauri build — no prepare-release / tagging.
#
# GitHub: workflow_dispatch checks out the ref. Local act: ACT=true skips checkout (use -b bind + run
# ./scripts/run-macos-arm64-build.sh so submodules are initialized before act copies the tree).
name: macOS ARM64 build
on:
workflow_dispatch:
permissions:
contents: read
jobs:
build-macos-arm64:
name: Build macOS aarch64 (signed)
runs-on: macos-latest
env:
GITHUB_TOKEN: ${{ secrets.XGH_TOKEN || github.token }}
steps:
- name: Checkout repository
if: ${{ env.ACT != 'true' }}
uses: actions/checkout@v4
- name: Set Xcode version
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: latest-stable
- name: Setup Node.js 24.x
uses: actions/setup-node@v4
with:
node-version: 24.x
cache: yarn
- name: Install Rust stable
uses: dtolnay/rust-toolchain@stable
with:
targets: aarch64-apple-darwin
- name: Cargo.lock fingerprint (deps only)
id: cargo-lock-fingerprint
shell: bash
run: |
echo "hash=$(tail -n +8 src-tauri/Cargo.lock | openssl dgst -sha256 | awk '{print $2}')" >> "$GITHUB_OUTPUT"
- name: Cache Cargo registry and git sources
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
key: ${{ runner.os }}-cargo-registry-${{ steps.cargo-lock-fingerprint.outputs.hash }}
restore-keys: |
${{ runner.os }}-cargo-registry-
- name: Install dependencies
run: yarn install --frozen-lockfile
- name: Install skills dependencies
run: cd skills && yarn install --frozen-lockfile
- name: Build skills
run: cd skills && yarn build
- name: Define Tauri configuration overrides
id: config-overrides
uses: actions/github-script@v7
env:
BASE_URL: ${{ vars.BASE_URL }}
UPDATER_PUBLIC_KEY: ${{ secrets.UPDATER_PUBLIC_KEY }}
WITH_UPDATER: 'true'
with:
github-token: ${{ secrets.XGH_TOKEN || github.token }}
script: |
const workspacePath = process.env.GITHUB_WORKSPACE.replace(/\\/g, '/');
const prefix = workspacePath.startsWith('/') ? 'file://' : 'file:///';
const moduleUrl = `${prefix}${workspacePath}/scripts/prepareTauriConfig.js`;
const { default: prepareTauriConfig } = await import(moduleUrl);
const config = prepareTauriConfig();
core.setOutput('json', JSON.stringify(config));
- name: Build frontend
run: yarn build
env:
NODE_ENV: production
VITE_BACKEND_URL: ${{ vars.VITE_BACKEND_URL }}
VITE_SENTRY_DSN: ${{ vars.VITE_SENTRY_DSN }}
VITE_DEBUG: ${{ vars.VITE_DEBUG }}
- name: Build sidecar core binary
shell: bash
run: |
cargo build \
--manifest-path rust-core/Cargo.toml \
--release \
--target aarch64-apple-darwin \
--bin openhuman-core
- name: Stage sidecar for Tauri bundler
shell: bash
run: |
mkdir -p src-tauri/binaries
cp rust-core/target/aarch64-apple-darwin/release/openhuman-core \
src-tauri/binaries/openhuman-core-aarch64-apple-darwin
chmod +x src-tauri/binaries/openhuman-core-aarch64-apple-darwin
- name: Build, package (no GitHub release upload)
uses: tauri-apps/tauri-action@v0.6.2
env:
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE_BASE64 }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
BASE_URL: ${{ vars.BASE_URL }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
WITH_UPDATER: 'true'
MACOSX_DEPLOYMENT_TARGET: '10.15'
with:
args: -c ${{ steps.config-overrides.outputs.json }} --target aarch64-apple-darwin -- -- --bin OpenHuman
includeDebug: false
includeRelease: true
includeUpdaterJson: false
-499
View File
@@ -1,499 +0,0 @@
# Terms:
# "build" - Compile web project using webpack.
# "package" - Produce a distributive package for a specific platform as a workflow artifact.
# "publish" - Send a package to corresponding store and GitHub release page.
# "release" - build + package + publish
#
# Jobs in this workflow will skip the "publish" step when `SHOULD_PUBLISH` is not set.
name: Package and publish (Legacy)
on:
workflow_dispatch:
# pull_request:
# branches:
# - main
env:
IS_PR: ${{ github.event_name == 'pull_request' }}
SHOULD_PUBLISH: ${{ github.event_name != 'pull_request' && github.ref == 'refs/heads/main' }}
PUBLISH_REPO: openhumanxyz/openhuman
XGH_TOKEN: ${{ secrets.XGH_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
jobs:
get-version:
runs-on: ubuntu-latest
outputs:
package-version: ${{ steps.extract-version.outputs.package-version }}
tag-name: ${{ steps.extract-version.outputs.tag-name }}
should-publish: ${{ steps.extract-version.outputs.should-publish }}
release-name: ${{ steps.extract-version.outputs.release-name }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 1
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.ref || github.ref }}
token: ${{ secrets.XGH_TOKEN_READ }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20.x
- name: Extract version and tag
id: extract-version
run: |
PACKAGE_VERSION=$(node -p "require('./package.json').version")
TAG_NAME="v${PACKAGE_VERSION}"
RELEASE_NAME="OpenHuman v${PACKAGE_VERSION}"
echo "package-version=$PACKAGE_VERSION" >> $GITHUB_OUTPUT
echo "tag-name=$TAG_NAME" >> $GITHUB_OUTPUT
echo "release-name=$RELEASE_NAME" >> $GITHUB_OUTPUT
echo "should-publish=$SHOULD_PUBLISH" >> $GITHUB_OUTPUT
echo "Extracted version: $PACKAGE_VERSION"
echo "Generated tag: $TAG_NAME"
echo "Generated release name: $RELEASE_NAME"
check-version:
runs-on: ubuntu-latest
needs: get-version
environment: ${{ github.ref == 'refs/heads/main' && 'Production' || '' }}
outputs:
should-skip: ${{ steps.check-release.outputs.should-skip }}
steps:
- name: Check if release already exists
id: check-release
env:
PACKAGE_VERSION: ${{ needs.get-version.outputs.package-version }}
TAG_NAME: ${{ needs.get-version.outputs.tag-name }}
run: |
# For non-main branches or when publishing is disabled, always continue
if [ -z "$SHOULD_PUBLISH" ]; then
echo "🚧 Publishing disabled (non-main branch)"
echo "should-skip=false" >> $GITHUB_OUTPUT
exit 0
fi
echo "Checking if release already exists for tag: $TAG_NAME"
RESPONSE=$(curl -s -w "\n%{http_code}" -H "Authorization: Bearer $XGH_TOKEN" \
-H "Accept: application/vnd.github.v3+json" \
"https://api.github.com/repos/$PUBLISH_REPO/releases/tags/$TAG_NAME")
HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
BODY=$(echo "$RESPONSE" | sed '$d')
if [ "$HTTP_CODE" = "404" ]; then
echo "🆕 No release found for version $PACKAGE_VERSION, will create new release"
echo "should-skip=false" >> $GITHUB_OUTPUT
elif [ "$HTTP_CODE" != "200" ]; then
echo "⚠️ Warning: Failed to check release (HTTP $HTTP_CODE). Response: $BODY"
echo "Continuing anyway..."
echo "should-skip=false" >> $GITHUB_OUTPUT
elif echo "$BODY" | jq -e '.tag_name' > /dev/null; then
IS_DRAFT=$(echo "$BODY" | jq -r '.draft')
if [ "$IS_DRAFT" = "false" ]; then
echo "✅ Published release already exists for version $PACKAGE_VERSION"
echo "should-skip=true" >> $GITHUB_OUTPUT
else
echo "📝 Draft release exists for version $PACKAGE_VERSION, will continue"
echo "should-skip=false" >> $GITHUB_OUTPUT
fi
else
echo "🆕 No release found for version $PACKAGE_VERSION, will create new release"
echo "should-skip=false" >> $GITHUB_OUTPUT
fi
create-release:
runs-on: ubuntu-latest
needs: [get-version, check-version]
environment: ${{ github.ref == 'refs/heads/main' && 'Production' || '' }}
if: github.ref == 'refs/heads/main' && needs.get-version.outputs.should-publish == 'true' && needs.check-version.outputs.should-skip != 'true'
permissions:
contents: write
outputs:
releaseId: ${{ steps.create-release.outputs.releaseId }}
steps:
- name: Create draft release
id: create-release
env:
PACKAGE_VERSION: ${{ needs.get-version.outputs.package-version }}
TAG_NAME: ${{ needs.get-version.outputs.tag-name }}
RELEASE_NAME: ${{ needs.get-version.outputs.release-name }}
XGH_TOKEN: ${{ secrets.XGH_TOKEN }}
run: |
echo "Creating draft release for tag: $TAG_NAME"
echo "Repository: $PUBLISH_REPO"
RELEASE_BODY="See the assets below to download this version."
RESPONSE=$(curl -s -w "\n%{http_code}" -X POST \
-H "Authorization: Bearer $XGH_TOKEN" \
-H "Accept: application/vnd.github.v3+json" \
-d '{"tag_name": "'"$TAG_NAME"'", "name": "'"$RELEASE_NAME"'", "draft": true, "body": "'"$RELEASE_BODY"'"}' \
"https://api.github.com/repos/$PUBLISH_REPO/releases")
HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
BODY=$(echo "$RESPONSE" | sed '$d')
echo "HTTP Status Code: $HTTP_CODE"
echo "Response body: $BODY"
if [ "$HTTP_CODE" != "201" ]; then
echo "Error: Failed to create release. HTTP $HTTP_CODE"
echo "Response: $BODY"
exit 1
fi
RELEASE_ID=$(echo "$BODY" | jq -r '.id')
echo "Extracted Release ID: $RELEASE_ID"
if [ "$RELEASE_ID" = "null" ] || [ -z "$RELEASE_ID" ]; then
echo "Error: Failed to extract release ID. Response was: $BODY"
exit 1
fi
echo "releaseId=$RELEASE_ID" >> $GITHUB_OUTPUT
package-tauri:
name: Build, package and publish Tauri
needs: [get-version, check-version, create-release]
if: ${{ !failure() && !cancelled() && needs.check-version.outputs.should-skip != 'true' }}
environment: ${{ github.ref == 'refs/heads/main' && 'Production' || '' }}
env:
GITHUB_TOKEN: ${{ secrets.XGH_TOKEN }}
permissions:
contents: write
strategy:
fail-fast: false
matrix:
settings:
- platform: 'macos-latest'
args: '--target aarch64-apple-darwin'
target: 'aarch64-apple-darwin'
- platform: 'macos-latest'
args: '--target x86_64-apple-darwin'
target: 'x86_64-apple-darwin'
- platform: 'ubuntu-22.04'
args: '--target x86_64-unknown-linux-gnu'
target: 'x86_64-unknown-linux-gnu'
runs-on: ${{ matrix.settings.platform }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 1
submodules: true
token: ${{ secrets.XGH_TOKEN_READ }}
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.ref || github.ref }}
- name: Set Xcode version
if: matrix.settings.platform == 'macos-latest'
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: latest-stable
- name: Setup Node.js 24.x
uses: actions/setup-node@v4
with:
node-version: 24.x
cache: 'yarn'
- name: Install Rust stable
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.settings.platform == 'macos-latest' && 'aarch64-apple-darwin,x86_64-apple-darwin' || '' }}
- name: Install Tauri dependencies (ubuntu only)
if: matrix.settings.platform == 'ubuntu-22.04'
run: |
sudo apt-get update
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf
# OpenMP is now disabled via Cargo.toml (default-features = false for llama-cpp-2)
# This avoids cross-compilation issues where Homebrew ARM64 OpenMP can't be used for x86_64 builds
- name: Cache Cargo registry
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
key: ${{ runner.os }}-cargo-${{ hashFiles('src-tauri/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-
- name: Cache Cargo build artifacts
uses: actions/cache@v4
with:
path: src-tauri/target
key: ${{ runner.os }}-cargo-target-${{ matrix.settings.platform }}-${{ hashFiles('src-tauri/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-target-${{ matrix.settings.platform }}-
- name: Cache node modules
id: yarn-cache
uses: actions/cache@v4
with:
path: node_modules
key: ${{ runner.os }}-build-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-build-
- name: Install dependencies
if: steps.yarn-cache.outputs.cache-hit != 'true'
run: yarn install --frozen-lockfile
- name: Cache skills node modules
id: skills-yarn-cache
uses: actions/cache@v4
with:
path: skills/node_modules
key: ${{ runner.os }}-skills-${{ hashFiles('skills/yarn.lock') }}
restore-keys: |
${{ runner.os }}-skills-
- name: Install skills dependencies
if: steps.skills-yarn-cache.outputs.cache-hit != 'true'
run: cd skills && yarn install --frozen-lockfile
- name: Build skills
run: cd skills && yarn build
- name: Extract repository owner and name
id: repository-info
if: needs.get-version.outputs.should-publish != ''
shell: bash
run: |
echo "owner=${PUBLISH_REPO%%/*}" >> $GITHUB_OUTPUT
echo "repo=${PUBLISH_REPO#*/}" >> $GITHUB_OUTPUT
- name: Define Tauri configuration overrides
id: config-overrides
uses: actions/github-script@v7
env:
BASE_URL: ${{ vars.BASE_URL }}
UPDATER_PUBLIC_KEY: ${{ secrets.UPDATER_PUBLIC_KEY }}
WITH_UPDATER: ${{ needs.get-version.outputs.should-publish != '' && 'true' || 'false' }}
with:
script: |
const workspacePath = process.env.GITHUB_WORKSPACE.replace(/\\/g, '/');
const prefix = workspacePath.startsWith('/') ? 'file://' : 'file:///';
const moduleUrl = `${prefix}${workspacePath}/scripts/prepareTauriConfig.js`;
const { default: prepareTauriConfig } = await import(moduleUrl)
const config = prepareTauriConfig();
const configJson = JSON.stringify(config);
console.log(configJson);
core.setOutput("json", configJson);
- name: Build frontend
run: yarn build
env:
NODE_ENV: production
VITE_BACKEND_URL: ${{ vars.VITE_BACKEND_URL }}
VITE_SENTRY_DSN: ${{ secrets.VITE_SENTRY_DSN }}
VITE_DEBUG: ${{ vars.VITE_DEBUG }}
- name: Build sidecar core binary
shell: bash
run: |
cargo build \
--manifest-path rust-core/Cargo.toml \
--release \
--target "$MATRIX_TARGET" \
--bin openhuman-core
env:
MATRIX_TARGET: ${{ matrix.settings.target }}
- name: Stage sidecar for Tauri bundler
shell: bash
run: |
mkdir -p src-tauri/binaries
SOURCE="rust-core/target/$MATRIX_TARGET/release/openhuman-core"
DEST="src-tauri/binaries/openhuman-core-$MATRIX_TARGET"
cp "$SOURCE" "$DEST"
chmod +x "$DEST"
env:
MATRIX_TARGET: ${{ matrix.settings.target }}
- name: Build, package and publish
uses: tauri-apps/tauri-action@v0.6.2
id: build-tauri
env:
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE_BASE64 }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
BASE_URL: ${{ vars.BASE_URL }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.UPDATER_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.UPDATER_PRIVATE_KEY_PASSWORD }}
WITH_UPDATER: ${{ needs.get-version.outputs.should-publish != '' && 'true' || 'false' }}
# macOS 10.15+ required for std::filesystem used by llama.cpp
MACOSX_DEPLOYMENT_TARGET: ${{ matrix.settings.platform == 'macos-latest' && '10.15' || '' }}
with:
args: '-c ${{ steps.config-overrides.outputs.json }} ${{ matrix.settings.args }} -- -- --bin OpenHuman'
includeDebug: ${{ needs.get-version.outputs.should-publish == '' && inputs.forceRelease != 'true' }}
includeRelease: ${{ needs.get-version.outputs.should-publish != '' || inputs.forceRelease == 'true' }}
# TDLib dylibs are now bundled natively via build.rs + tauri.conf.json macOS.frameworks
# No post-build scripts needed - Tauri bundler handles copying and signing
releaseId: ${{ needs.create-release.outputs.releaseId }}
owner: openhumanxyz
repo: openhuman
- name: Get file info
id: file-info
shell: bash
run: |
FULL_PATH=$(echo "${{ fromJSON(steps.build-tauri.outputs.artifactPaths)[0] }}")
FILENAME=$(basename "$FULL_PATH")
NAME_WITH_EXT="${FILENAME%.*}"
VERSION="${{ needs.get-version.outputs.package-version }}"
TAG_NAME="${{ needs.get-version.outputs.tag-name }}"
# Remove version/tag patterns from name
# Patterns to remove: v0.14.0-1, 0.14.0-1, v0.14.0, 0.14.0, etc.
# Package managers add release numbers like -1, -2, etc.
NAME="$NAME_WITH_EXT"
# Remove version-release pattern (0.14.0-1, 0.14.0-2, etc.) - escape dots for regex
VERSION_ESCAPED=$(echo "$VERSION" | sed 's/\./\\./g')
NAME=$(echo "$NAME" | sed -E "s/[_-]?${VERSION_ESCAPED}-[0-9]+[_-]?//g")
# Remove tag-release pattern (v0.14.0-1, v0.14.0-2, etc.)
TAG_ESCAPED=$(echo "$TAG_NAME" | sed 's/\./\\./g')
NAME=$(echo "$NAME" | sed -E "s/[_-]?${TAG_ESCAPED}-[0-9]+[_-]?//g")
# Remove tag name (v0.14.0) with various separators
NAME=$(echo "$NAME" | sed -E "s/[_-]?${TAG_ESCAPED}[_-]?//g")
# Remove version (0.14.0) with various separators
NAME=$(echo "$NAME" | sed -E "s/[_-]?${VERSION_ESCAPED}[_-]?//g")
# Remove version with dots replaced by separators (0_14_0, 0-14-0)
VERSION_DASH=$(echo "$VERSION" | tr '.' '-')
VERSION_UNDERSCORE=$(echo "$VERSION" | tr '.' '_')
NAME=$(echo "$NAME" | sed -E "s/[_-]?v?${VERSION_DASH}[_-]?//g")
NAME=$(echo "$NAME" | sed -E "s/[_-]?v?${VERSION_UNDERSCORE}[_-]?//g")
# Clean up any double separators or trailing/leading separators
NAME=$(echo "$NAME" | sed -E 's/[_-]{2,}/_/g' | sed 's/^[_-]//' | sed 's/[_-]$//')
FILE_PATH=$(cd "$(dirname "$FULL_PATH")" && pwd)
# Extract architecture from target (e.g., aarch64-apple-darwin -> aarch64)
ARCHITECTURE=$(echo "${{ matrix.settings.target }}" | cut -d'-' -f1)
echo "name=$NAME" >> $GITHUB_OUTPUT
echo "filename=$FILENAME" >> $GITHUB_OUTPUT
echo "architecture=$ARCHITECTURE" >> $GITHUB_OUTPUT
echo "path=$FILE_PATH" >> $GITHUB_OUTPUT
# MacOS release — upload the DMG after TDLib bundling and re-signing
- name: Upload release asset (MacOS)
if: matrix.settings.platform == 'macos-latest' && needs.get-version.outputs.should-publish != ''
shell: bash
run: |
SANITIZED_FILENAME=$(echo "${{ steps.file-info.outputs.name }}" | sed 's/ /./g')
PUBLISH_FILE_NAME="$SANITIZED_FILENAME-${{ steps.file-info.outputs.architecture }}.dmg"
FILE_PATH="${{ steps.file-info.outputs.path }}/${{ steps.file-info.outputs.filename }}"
RELEASE_ID="${{ needs.create-release.outputs.releaseId }}"
curl -X POST -H "Authorization: Bearer $XGH_TOKEN" \
-H "Content-Type: application/octet-stream" \
--data-binary "@$FILE_PATH" \
"https://uploads.github.com/repos/$PUBLISH_REPO/releases/$RELEASE_ID/assets?name=$PUBLISH_FILE_NAME"
- name: Upload artifact (MacOS)
if: matrix.settings.platform == 'macos-latest'
uses: actions/upload-artifact@v4
with:
name: ${{ steps.file-info.outputs.name }}-${{ steps.file-info.outputs.architecture }}.dmg
path: ${{ steps.file-info.outputs.path }}/${{ steps.file-info.outputs.filename }}
# Linux release
- name: Upload Linux artifact (Linux)
if: matrix.settings.platform == 'ubuntu-22.04'
uses: actions/upload-artifact@v4
with:
name: ${{ steps.file-info.outputs.filename }}
path: ${{ steps.file-info.outputs.path }}/${{ steps.file-info.outputs.filename }}
publish-release:
runs-on: ubuntu-latest
# needs: [get-version, check-version, create-release, package-tauri, package-android]
needs: [get-version, check-version, create-release, package-tauri]
environment: ${{ github.ref == 'refs/heads/main' && 'Production' || '' }}
if: github.ref == 'refs/heads/main' && needs.get-version.outputs.should-publish == 'true' && needs.check-version.outputs.should-skip != 'true'
permissions:
contents: write
env:
RELEASE_ID: ${{ needs.create-release.outputs.releaseId }}
XGH_TOKEN: ${{ secrets.XGH_TOKEN }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 1
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.ref || github.ref }}
- name: Publish release
run: |
RESPONSE=$(curl -s -w "\n%{http_code}" -X PATCH \
-H "Authorization: Bearer $XGH_TOKEN" \
-H "Accept: application/vnd.github.v3+json" \
-d '{"draft": false}' \
"https://api.github.com/repos/$PUBLISH_REPO/releases/$RELEASE_ID")
HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
BODY=$(echo "$RESPONSE" | sed '$d')
if [ "$HTTP_CODE" != "200" ]; then
echo "Error: Failed to publish release. HTTP $HTTP_CODE"
echo "Response: $BODY"
exit 1
fi
echo "Release published successfully"
- name: Update Gist with JSON
run: |
ASSET_ID=$(curl -s -H "Authorization: Bearer $XGH_TOKEN" \
-H "Accept: application/vnd.github.v3+json" \
"https://api.github.com/repos/$PUBLISH_REPO/releases/$RELEASE_ID/assets" | jq -r '.[] | select(.name == "latest.json") | .id')
JSON_CONTENT=$(curl -sSL -H "Accept: application/octet-stream" \
-H "Authorization: Bearer $XGH_TOKEN" \
"https://api.github.com/repos/$PUBLISH_REPO/releases/assets/$ASSET_ID")
GIST_CONTENT=$(jq -n --arg json "$JSON_CONTENT" '{"files":{"updater.json":{"content":$json}}}')
curl -X PATCH -H "Authorization: Bearer $XGH_TOKEN" \
-H "Accept: application/vnd.github.v3+json" \
-d "$GIST_CONTENT" \
"https://api.github.com/gists/$UPDATER_GIST_ID"
delete-release-on-failure:
name: Delete draft release on build/publish failure
runs-on: ubuntu-latest
needs: [get-version, create-release, package-tauri, publish-release]
if: failure() && needs.create-release.result == 'success' && needs.create-release.outputs.releaseId != ''
permissions:
contents: write
env:
RELEASE_ID: ${{ needs.create-release.outputs.releaseId }}
TAG_NAME: ${{ needs.get-version.outputs.tag-name }}
XGH_TOKEN: ${{ secrets.XGH_TOKEN }}
steps:
- name: Delete draft release
run: |
echo "Deleting draft release $RELEASE_ID after build or publish failure"
RESPONSE=$(curl -s -w "\n%{http_code}" -X DELETE \
-H "Authorization: Bearer $XGH_TOKEN" \
-H "Accept: application/vnd.github.v3+json" \
"https://api.github.com/repos/$PUBLISH_REPO/releases/$RELEASE_ID")
HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
BODY=$(echo "$RESPONSE" | sed '$d')
if [ "$HTTP_CODE" = "204" ] || [ "$HTTP_CODE" = "404" ]; then
echo "Release deleted (or already gone)"
else
echo "Warning: Delete release returned HTTP $HTTP_CODE. Response: $BODY"
fi
- name: Delete tag
run: |
echo "Deleting tag $TAG_NAME"
RESPONSE=$(curl -s -w "\n%{http_code}" -X DELETE \
-H "Authorization: Bearer $XGH_TOKEN" \
-H "Accept: application/vnd.github.v3+json" \
"https://api.github.com/repos/$PUBLISH_REPO/git/refs/tags/$TAG_NAME")
HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
if [ "$HTTP_CODE" = "204" ] || [ "$HTTP_CODE" = "404" ]; then
echo "Tag deleted (or already gone)"
else
echo "Warning: Delete tag returned HTTP $HTTP_CODE"
fi
+46 -12
View File
@@ -305,27 +305,57 @@ jobs:
VITE_SENTRY_DSN: ${{ vars.VITE_SENTRY_DSN }}
VITE_DEBUG: ${{ vars.VITE_DEBUG }}
- name: Resolve core manifest and binary names
id: core-paths
shell: bash
run: |
if [ -f "openhuman_core/Cargo.toml" ]; then
CORE_DIR="openhuman_core"
elif [ -f "rust-core/Cargo.toml" ]; then
CORE_DIR="rust-core"
else
echo "No core Cargo manifest found (expected openhuman_core/Cargo.toml or rust-core/Cargo.toml)"
exit 1
fi
SIDE_CAR_BASE="$(node -e "const fs=require('fs');const c=JSON.parse(fs.readFileSync('src-tauri/tauri.conf.json','utf8'));const b=(c.bundle&&Array.isArray(c.bundle.externalBin)&&c.bundle.externalBin[0])||'binaries/openhuman';process.stdout.write(String(b).split('/').pop());")"
CORE_BIN_NAME="${SIDE_CAR_BASE}"
echo "core_dir=$CORE_DIR" >> "$GITHUB_OUTPUT"
echo "core_manifest=$CORE_DIR/Cargo.toml" >> "$GITHUB_OUTPUT"
# Cargo workspace: release artifacts are under repo root target/, not <member>/target/
echo "core_target_dir=target/$MATRIX_TARGET/release" >> "$GITHUB_OUTPUT"
echo "core_bin_name=$CORE_BIN_NAME" >> "$GITHUB_OUTPUT"
echo "sidecar_base=$SIDE_CAR_BASE" >> "$GITHUB_OUTPUT"
env:
MATRIX_TARGET: ${{ matrix.settings.target }}
- name: Build sidecar core binary
shell: bash
run: |
cargo build \
--manifest-path rust-core/Cargo.toml \
--manifest-path "$CORE_MANIFEST" \
--release \
--target "$MATRIX_TARGET" \
--bin openhuman-core
--bin "$CORE_BIN_NAME"
env:
MATRIX_TARGET: ${{ matrix.settings.target }}
CORE_MANIFEST: ${{ steps.core-paths.outputs.core_manifest }}
CORE_BIN_NAME: ${{ steps.core-paths.outputs.core_bin_name }}
- name: Stage sidecar for Tauri bundler
shell: bash
run: |
mkdir -p src-tauri/binaries
SOURCE="rust-core/target/$MATRIX_TARGET/release/openhuman-core"
DEST="src-tauri/binaries/openhuman-core-$MATRIX_TARGET"
SOURCE="$CORE_TARGET_DIR/$CORE_BIN_NAME"
DEST="src-tauri/binaries/$SIDECAR_BASE-$MATRIX_TARGET"
cp "$SOURCE" "$DEST"
chmod +x "$DEST"
env:
MATRIX_TARGET: ${{ matrix.settings.target }}
CORE_TARGET_DIR: ${{ steps.core-paths.outputs.core_target_dir }}
CORE_BIN_NAME: ${{ steps.core-paths.outputs.core_bin_name }}
SIDECAR_BASE: ${{ steps.core-paths.outputs.sidecar_base }}
- name: Build, package and upload to release
uses: tauri-apps/tauri-action@v0.6.2
@@ -364,32 +394,36 @@ jobs:
APP_PATH="src-tauri/target/${{ matrix.settings.target }}/release/bundle/macos/OpenHuman.app"
echo "Inspecting bundle at: $APP_PATH"
ls -la "$APP_PATH/Contents/MacOS"
ls -la "$APP_PATH/Contents/Resources" | grep openhuman-core || true
if [ -f "$APP_PATH/Contents/MacOS/openhuman-core" ]; then
ls -la "$APP_PATH/Contents/Resources" | grep openhuman || true
if [ -f "$APP_PATH/Contents/MacOS/openhuman" ]; then
echo "Unexpected standalone core binary found in MacOS dir"
exit 1
fi
if ! ls "$APP_PATH/Contents/Resources"/openhuman-core-* >/dev/null 2>&1; then
if ! ls "$APP_PATH/Contents/Resources"/openhuman-* >/dev/null 2>&1; then
echo "Sidecar core binary missing from app resources"
exit 1
fi
- name: Build standalone CLI binary
- name: Build standalone core CLI binary
shell: bash
run: |
cargo build --manifest-path rust-core/Cargo.toml --release --target "$MATRIX_TARGET" --bin openhuman-cli
cargo build --manifest-path "$CORE_MANIFEST" --release --target "$MATRIX_TARGET" --bin "$CORE_BIN_NAME"
env:
MATRIX_TARGET: ${{ matrix.settings.target }}
CORE_MANIFEST: ${{ steps.core-paths.outputs.core_manifest }}
CORE_BIN_NAME: ${{ steps.core-paths.outputs.core_bin_name }}
- name: Resolve standalone CLI artifact path
- name: Resolve standalone core CLI artifact path
id: cli-paths
shell: bash
run: |
BASE_DIR="rust-core/target/$MATRIX_TARGET/release"
BASE_DIR="$CORE_TARGET_DIR"
echo "base_dir=$BASE_DIR" >> "$GITHUB_OUTPUT"
echo "cli_path=$BASE_DIR/openhuman-cli" >> "$GITHUB_OUTPUT"
echo "cli_path=$BASE_DIR/$CORE_BIN_NAME" >> "$GITHUB_OUTPUT"
env:
MATRIX_TARGET: ${{ matrix.settings.target }}
CORE_TARGET_DIR: ${{ steps.core-paths.outputs.core_target_dir }}
CORE_BIN_NAME: ${{ steps.core-paths.outputs.core_bin_name }}
- name: Upload standalone CLI artifacts
uses: actions/upload-artifact@v4
+84
View File
@@ -54,3 +54,87 @@ jobs:
name: coverage-report
path: coverage
retention-days: 7
rust-tests:
name: Run Rust Tests
runs-on: ubuntu-22.04
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Install Rust stable
uses: dtolnay/rust-toolchain@stable
with:
targets: x86_64-unknown-linux-gnu
- name: Install Tauri build dependencies
run: |
sudo apt-get update
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf
# Match build.yml: lockfile body only (skip workspace version header noise)
- name: Cargo.lock fingerprint (deps only)
id: cargo-lock-fingerprint
shell: bash
run: |
echo "hash=$(tail -n +8 Cargo.lock | openssl dgst -sha256 | awk '{print $2}')" >> "$GITHUB_OUTPUT"
- name: Cache Cargo registry and git sources
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
key: ${{ runner.os }}-rust-test-cargo-${{ steps.cargo-lock-fingerprint.outputs.hash }}
restore-keys: |
${{ runner.os }}-rust-test-cargo-
- name: Test rust-core (openhuman-core)
run: cargo test -p openhuman-core
- name: Test src-tauri (OpenHuman)
run: cargo test -p OpenHuman
e2e-macos:
name: E2E (macOS / Appium)
runs-on: macos-latest
timeout-minutes: 90
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 1
submodules: recursive
- name: Setup Node.js 24.x
uses: actions/setup-node@v4
with:
node-version: 24.x
cache: 'yarn'
- name: Install Rust stable
uses: dtolnay/rust-toolchain@stable
- name: Install dependencies
run: yarn install --frozen-lockfile
- name: Install skills dependencies
run: cd skills && yarn install --frozen-lockfile
- name: Ensure .env exists for E2E build
run: touch .env
- name: Install Appium and mac2 driver
run: |
npm install -g appium
appium driver install mac2
- name: Build E2E app bundle
run: yarn test:e2e:build
env:
E2E_SKIP_CARGO_CLEAN: '1'
- name: Run all E2E flows
run: yarn test:e2e:all:flows
-197
View File
@@ -1,197 +0,0 @@
name: Update Changelog
on:
push:
branches:
- main
permissions:
contents: write
concurrency:
group: update-changelog
cancel-in-progress: false
jobs:
update-changelog:
runs-on: ubuntu-latest
# Skip if commit message contains [skip ci] or is a version bump commit
if: "!contains(github.event.head_commit.message, '[skip ci]') && !contains(github.event.head_commit.message, 'chore: bump version')"
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Configure Git
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
- name: Fetch tags
run: |
git fetch origin --tags
- name: Calculate next version
id: next-version
run: |
# Get the latest version tag (format: v1.2.3)
LATEST_TAG=$(git tag -l "v*" | sort -V | tail -n 1 || echo "")
if [ -z "$LATEST_TAG" ]; then
# No tags found, start with v1.0.0
NEXT_VERSION="v1.0.0"
LATEST_TAG="(none)"
else
# Extract version number (remove 'v' prefix)
VERSION="${LATEST_TAG#v}"
# Split into major.minor.patch
IFS='.' read -r MAJOR MINOR PATCH <<< "$VERSION"
# Ensure we have valid numbers (default to 0 if empty)
MAJOR=${MAJOR:-0}
MINOR=${MINOR:-0}
PATCH=${PATCH:-0}
# Increment minor version and reset patch to 0
MINOR=$((MINOR + 1))
PATCH=0
NEXT_VERSION="v${MAJOR}.${MINOR}.${PATCH}"
fi
echo "Latest tag: $LATEST_TAG"
echo "Predicted next version: $NEXT_VERSION"
echo "version=$NEXT_VERSION" >> $GITHUB_OUTPUT
- name: Get commits since last tag
id: get-commits
run: |
# Get the latest actual tag (not the predicted one)
LATEST_TAG=$(git tag -l "v*" | sort -V | tail -n 1 || echo "")
# Get commits since the last tag (or all commits if no tag exists)
if [ -n "$LATEST_TAG" ] && git rev-parse "$LATEST_TAG" >/dev/null 2>&1; then
# Tag exists, get commits since that tag
git log "$LATEST_TAG"..HEAD --pretty=format:"- %s (%h)" --no-merges > commits.txt || echo "- $(git log -1 --pretty=format:'%s (%h)')" > commits.txt
else
# No tag exists, get all commits
git log --pretty=format:"- %s (%h)" --no-merges > commits.txt || echo "- $(git log -1 --pretty=format:'%s (%h)')" > commits.txt
fi
# If file is empty, use current commit
if [ ! -s commits.txt ]; then
echo "- $(git log -1 --pretty=format:'%s (%h)')" > commits.txt
fi
- name: Get current date
id: date
run: |
echo "date=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT
- name: Update CHANGELOG.md
run: |
CHANGELOG_PATH="publish/CHANGELOG.md"
NEXT_VERSION="${{ steps.next-version.outputs.version }}"
RELEASE_DATE="${{ steps.date.outputs.date }}"
# Create changelog directory if it doesn't exist
mkdir -p publish
# Check if changelog exists and has content
if [ ! -f "$CHANGELOG_PATH" ] || [ ! -s "$CHANGELOG_PATH" ]; then
# Create new changelog with header
cat > "$CHANGELOG_PATH" <<'EOF'
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
### Added
- Initial changelog
EOF
fi
# Check if this version already exists in changelog
if grep -q "## \[$NEXT_VERSION\]" "$CHANGELOG_PATH"; then
echo "Version $NEXT_VERSION already exists in CHANGELOG.md. Skipping update."
exit 0
fi
# Use Python to update the changelog
python3 <<PYTHON_SCRIPT
changelog_path = "$CHANGELOG_PATH"
next_version = "$NEXT_VERSION"
release_date = "$RELEASE_DATE"
# Read commits
with open("commits.txt", "r") as f:
commits = f.read().strip()
# Read existing changelog
with open(changelog_path, "r") as f:
lines = f.readlines()
# Create new version entry
new_entry = f"""## [{next_version}] - {release_date}
### Changed
{commits}
"""
# Find [Unreleased] section and insert after it
output_lines = []
inserted = False
i = 0
while i < len(lines):
line = lines[i]
output_lines.append(line)
# Check if this is the [Unreleased] header line
if line.strip() == "## [Unreleased]":
# Find the end of the [Unreleased] section (next ## header or end of file)
i += 1
while i < len(lines) and not lines[i].startswith("##"):
output_lines.append(lines[i])
i += 1
# Insert new entry after the [Unreleased] section
output_lines.append("\n")
output_lines.append(new_entry)
inserted = True
# Continue with remaining lines
continue
i += 1
# If [Unreleased] not found, prepend
if not inserted:
output_lines = [new_entry, "\n"] + lines
# Write updated changelog
with open(changelog_path, "w") as f:
f.writelines(output_lines)
print(f"Updated CHANGELOG.md with version {next_version}")
PYTHON_SCRIPT
- name: Commit and push changes
run: |
git add publish/CHANGELOG.md
# Check if there are changes to commit
if git diff --staged --quiet; then
echo "No changes to commit."
exit 0
fi
git commit -m "chore: update changelog for ${{ steps.next-version.outputs.version }} [skip ci]"
git push origin develop
+1 -1
View File
@@ -32,6 +32,6 @@ set -e
# Exit with error if any command still fails after fixes
if [ $FORMAT_EXIT -ne 0 ] || [ $LINT_EXIT -ne 0 ] || [ $COMPILE_EXIT -ne 0 ]; then
echo "Pre-push checks failed. Please fix format, lint, and/or TypeScript errors before pushing."
echo "Pre-push checks failed. Please fix format (Prettier + cargo fmt), lint, and/or TypeScript errors before pushing."
exit 1
fi
+1
View File
@@ -2,6 +2,7 @@ node_modules
dist
coverage
src-tauri
rust-core
skills
*.config.js
*.config.ts
+6 -11
View File
@@ -25,14 +25,16 @@
"test:unit:watch": "vitest --config test/vitest.config.ts",
"test:watch": "vitest --config test/vitest.config.ts",
"test:coverage": "vitest run --config test/vitest.config.ts --coverage",
"test:rust": "source $HOME/.cargo/env 2>/dev/null; cargo test --manifest-path src-tauri/Cargo.toml",
"test:rust": "source $HOME/.cargo/env 2>/dev/null; cargo test --workspace",
"test:e2e:build": "bash scripts/e2e-build.sh",
"test:e2e:login": "bash scripts/e2e-login.sh",
"test:e2e:auth": "bash scripts/e2e-auth.sh",
"test:e2e": "yarn test:e2e:build && yarn test:e2e:login && yarn test:e2e:auth",
"test:e2e:all:flows": "bash scripts/e2e-run-all-flows.sh",
"test:e2e:all": "yarn test:e2e:build && yarn test:e2e:all:flows",
"test:all": "yarn test:coverage && yarn test:rust && yarn test:e2e",
"format": "prettier --write .",
"format:check": "prettier --check .",
"format": "prettier --write . && cargo fmt --all",
"format:check": "prettier --check . && cargo fmt --all --check",
"lint": "eslint . --ext .ts,.tsx",
"lint:fix": "eslint . --ext .ts,.tsx --fix",
"skills:build": "cd skills && yarn build",
@@ -51,16 +53,12 @@
"@tauri-apps/plugin-deep-link": "^2",
"@tauri-apps/plugin-opener": "^2",
"@tauri-apps/plugin-os": "^2.3.2",
"@tauri-apps/plugin-shell": "~2",
"@types/react-router-dom": "^5.3.3",
"@types/three": "^0.183.1",
"buffer": "^6.0.3",
"debug": "^4.4.3",
"immer": "^11.1.3",
"lottie-react": "^2.4.1",
"os-browserify": "^0.3.0",
"process": "^0.11.10",
"qrcode.react": "^4.2.0",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-markdown": "^10.1.0",
@@ -69,10 +67,8 @@
"redux-logger": "^3.0.6",
"redux-persist": "^6.0.0",
"socket.io-client": "^4.8.3",
"telegram": "^2.26.22",
"three": "^0.183.2",
"util": "^0.12.5",
"zustand": "^5.0.10"
"util": "^0.12.5"
},
"devDependencies": {
"@eslint/js": "^9.39.2",
@@ -82,7 +78,6 @@
"@testing-library/dom": "^10.4.1",
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.2",
"@testing-library/user-event": "^14.6.1",
"@trivago/prettier-plugin-sort-imports": "^6.0.2",
"@types/debug": "^4.1.12",
"@types/node": "^25.0.10",
-388
View File
@@ -1,388 +0,0 @@
use clap::{Args, Parser, Subcommand};
use serde::{Deserialize, Serialize};
use serde_json::json;
#[derive(Debug, Serialize)]
struct RpcRequest {
jsonrpc: &'static str,
id: u64,
method: String,
params: serde_json::Value,
}
#[derive(Debug, Deserialize)]
struct RpcResponse {
result: Option<serde_json::Value>,
error: Option<RpcError>,
}
#[derive(Debug, Deserialize)]
struct RpcError {
code: i64,
message: String,
data: Option<serde_json::Value>,
}
#[derive(Debug, Parser)]
#[command(name = "openhuman-cli")]
#[command(about = "CLI for the OpenHuman core RPC server")]
struct Cli {
/// Core RPC endpoint URL
#[arg(long, global = true)]
rpc_url: Option<String>,
#[command(subcommand)]
command: Command,
}
#[derive(Debug, Subcommand)]
enum Command {
/// Check core health
Ping,
/// Print core version
Version,
/// Get health snapshot
Health,
/// Get runtime flags
RuntimeFlags,
/// Config operations
Config {
#[command(subcommand)]
command: ConfigCommand,
},
/// Service operations
Service {
#[command(subcommand)]
command: ServiceCommand,
},
/// Doctor operations
Doctor {
#[command(subcommand)]
command: DoctorCommand,
},
/// Integrations operations
Integrations {
#[command(subcommand)]
command: IntegrationsCommand,
},
/// Send one-shot agent message
AgentChat(AgentChatArgs),
/// Hardware operations
Hardware {
#[command(subcommand)]
command: HardwareCommand,
},
/// Encrypt a secret
Encrypt { plaintext: String },
/// Decrypt a secret
Decrypt { ciphertext: String },
/// Toggle browser allow-all runtime flag
BrowserAllowAll {
#[arg(long)]
enabled: bool,
},
/// Refresh model catalog
ModelsRefresh {
#[arg(long)]
provider: Option<String>,
#[arg(long, default_value_t = false)]
force: bool,
},
/// Migrate OpenClaw memory
MigrateOpenclaw {
#[arg(long)]
source_workspace: Option<String>,
#[arg(long, default_value_t = true)]
dry_run: bool,
},
}
#[derive(Debug, Subcommand)]
enum ConfigCommand {
/// Get full config snapshot
Get,
}
#[derive(Debug, Subcommand)]
enum ServiceCommand {
Install,
Start,
Stop,
Status,
Reinstall,
Uninstall,
}
#[derive(Debug, Subcommand)]
enum DoctorCommand {
/// Run doctor checks
Report,
/// Probe model catalog
Models {
#[arg(long)]
provider: Option<String>,
#[arg(long, default_value_t = true)]
use_cache: bool,
},
}
#[derive(Debug, Subcommand)]
enum IntegrationsCommand {
/// List integrations
List,
/// Get one integration info
Info {
#[arg(long)]
name: String,
},
}
#[derive(Debug, Subcommand)]
enum HardwareCommand {
/// Discover connected hardware
Discover,
/// Introspect one device path
Introspect {
#[arg(long)]
path: String,
},
}
#[derive(Debug, Args)]
struct AgentChatArgs {
message: String,
#[arg(long)]
provider: Option<String>,
#[arg(long)]
model: Option<String>,
#[arg(long)]
temperature: Option<f64>,
}
fn endpoint(cli: &Cli) -> String {
if let Some(url) = &cli.rpc_url {
return url.clone();
}
std::env::var("OPENHUMAN_CORE_RPC_URL")
.unwrap_or_else(|_| "http://127.0.0.1:7788/rpc".to_string())
}
fn call(url: &str, method: &str, params: serde_json::Value) -> Result<serde_json::Value, String> {
let req = RpcRequest {
jsonrpc: "2.0",
id: 1,
method: method.to_string(),
params,
};
let client = reqwest::blocking::Client::new();
let resp = client
.post(url)
.json(&req)
.send()
.map_err(|e| format!("request failed: {e}"))?;
let payload: RpcResponse = resp.json().map_err(|e| format!("invalid response: {e}"))?;
if let Some(err) = payload.error {
return Err(format!(
"rpc error {}: {}{}",
err.code,
err.message,
err.data.map(|d| format!(" ({d})")).unwrap_or_default()
));
}
Ok(payload.result.unwrap_or(serde_json::Value::Null))
}
fn execute(cli: Cli) -> Result<serde_json::Value, String> {
let url = endpoint(&cli);
match cli.command {
Command::Ping => call(&url, "core.ping", serde_json::json!({})),
Command::Version => call(&url, "core.version", serde_json::json!({})),
Command::Health => call(&url, "openhuman.health_snapshot", serde_json::json!({})),
Command::RuntimeFlags => call(&url, "openhuman.get_runtime_flags", serde_json::json!({})),
Command::Config { command } => match command {
ConfigCommand::Get => call(&url, "openhuman.get_config", serde_json::json!({})),
},
Command::Service { command } => match command {
ServiceCommand::Install => local_service_install(),
ServiceCommand::Start => local_service_start(),
ServiceCommand::Stop => local_service_stop(),
ServiceCommand::Status => local_service_status(),
ServiceCommand::Reinstall => {
let _ = local_service_uninstall()?;
local_service_install()
}
ServiceCommand::Uninstall => local_service_uninstall(),
},
Command::Doctor { command } => match command {
DoctorCommand::Report => call(&url, "openhuman.doctor_report", serde_json::json!({})),
DoctorCommand::Models {
provider,
use_cache,
} => call(
&url,
"openhuman.doctor_models",
serde_json::json!({
"provider_override": provider,
"use_cache": use_cache,
}),
),
},
Command::Integrations { command } => match command {
IntegrationsCommand::List => {
call(&url, "openhuman.list_integrations", serde_json::json!({}))
}
IntegrationsCommand::Info { name } => call(
&url,
"openhuman.get_integration_info",
serde_json::json!({ "name": name }),
),
},
Command::AgentChat(args) => call(
&url,
"openhuman.agent_chat",
serde_json::json!({
"message": args.message,
"provider_override": args.provider,
"model_override": args.model,
"temperature": args.temperature,
}),
),
Command::Hardware { command } => match command {
HardwareCommand::Discover => {
call(&url, "openhuman.hardware_discover", serde_json::json!({}))
}
HardwareCommand::Introspect { path } => call(
&url,
"openhuman.hardware_introspect",
serde_json::json!({ "path": path }),
),
},
Command::Encrypt { plaintext } => call(
&url,
"openhuman.encrypt_secret",
serde_json::json!({ "plaintext": plaintext }),
),
Command::Decrypt { ciphertext } => call(
&url,
"openhuman.decrypt_secret",
serde_json::json!({ "ciphertext": ciphertext }),
),
Command::BrowserAllowAll { enabled } => call(
&url,
"openhuman.set_browser_allow_all",
serde_json::json!({ "enabled": enabled }),
),
Command::ModelsRefresh { provider, force } => call(
&url,
"openhuman.models_refresh",
serde_json::json!({
"provider_override": provider,
"force": force,
}),
),
Command::MigrateOpenclaw {
source_workspace,
dry_run,
} => call(
&url,
"openhuman.migrate_openclaw",
serde_json::json!({
"source_workspace": source_workspace,
"dry_run": dry_run,
}),
),
}
}
fn load_config() -> Result<openhuman_core::openhuman::config::Config, String> {
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.map_err(|e| format!("failed to build runtime: {e}"))?;
runtime
.block_on(openhuman_core::openhuman::config::Config::load_or_init())
.map_err(|e| format!("failed to load config: {e}"))
}
fn status_value(
status: openhuman_core::openhuman::service::ServiceStatus,
) -> Result<serde_json::Value, String> {
serde_json::to_value(status).map_err(|e| format!("failed to serialize service status: {e}"))
}
fn command_response_json(
status: openhuman_core::openhuman::service::ServiceStatus,
log: &str,
) -> Result<serde_json::Value, String> {
Ok(json!({
"result": status_value(status)?,
"logs": [log],
}))
}
fn local_service_install() -> Result<serde_json::Value, String> {
let config = load_config()?;
let status = openhuman_core::openhuman::service::install(&config)
.map_err(|e| format!("service install failed: {e}"))?;
command_response_json(status, "service install completed")
}
fn local_service_start() -> Result<serde_json::Value, String> {
let config = load_config()?;
let status = openhuman_core::openhuman::service::start(&config)
.map_err(|e| format!("service start failed: {e}"))?;
command_response_json(status, "service start completed")
}
fn local_service_stop() -> Result<serde_json::Value, String> {
let config = load_config()?;
let status = openhuman_core::openhuman::service::stop(&config)
.map_err(|e| format!("service stop failed: {e}"))?;
command_response_json(status, "service stop completed")
}
fn local_service_status() -> Result<serde_json::Value, String> {
let config = load_config()?;
let status = openhuman_core::openhuman::service::status(&config)
.map_err(|e| format!("service status failed: {e}"))?;
command_response_json(status, "service status fetched")
}
fn local_service_uninstall() -> Result<serde_json::Value, String> {
let config = load_config()?;
let status = openhuman_core::openhuman::service::uninstall(&config)
.map_err(|e| format!("service uninstall failed: {e}"))?;
command_response_json(status, "service uninstall completed")
}
fn main() {
let cli = Cli::parse();
match execute(cli) {
Ok(value) => {
println!(
"{}",
serde_json::to_string_pretty(&value).unwrap_or_else(|_| "null".to_string())
);
}
Err(err) => {
eprintln!("{err}");
std::process::exit(1);
}
}
}
-7
View File
@@ -1,7 +0,0 @@
fn main() {
let args: Vec<String> = std::env::args().skip(1).collect();
if let Err(err) = openhuman_core::core_server::run_from_cli_args(&args) {
eprintln!("openhuman-core failed: {err}");
std::process::exit(1);
}
}
+334
View File
@@ -0,0 +1,334 @@
use clap::{Args, Parser, Subcommand};
use serde_json::json;
#[derive(Debug, Parser)]
#[command(name = "openhuman")]
#[command(about = "OpenHuman core CLI")]
struct Cli {
#[command(subcommand)]
command: Command,
}
#[derive(Debug, Subcommand)]
enum Command {
/// Run JSON-RPC server
Serve {
#[arg(long)]
port: Option<u16>,
},
/// Check core health
Ping,
/// Print core version
Version,
/// Get health snapshot
Health,
/// Get runtime flags
RuntimeFlags,
/// Get security policy info
SecurityPolicy,
/// Generic JSON-RPC style method call
Call {
#[arg(long)]
method: String,
#[arg(long, default_value = "{}")]
params: String,
},
/// Config operations
Config {
#[command(subcommand)]
command: ConfigCommand,
},
/// Service operations
Service {
#[command(subcommand)]
command: ServiceCommand,
},
/// Doctor operations
Doctor {
#[command(subcommand)]
command: DoctorCommand,
},
/// Integrations operations
Integrations {
#[command(subcommand)]
command: IntegrationsCommand,
},
/// Send one-shot agent message
AgentChat(AgentChatArgs),
/// Hardware operations
Hardware {
#[command(subcommand)]
command: HardwareCommand,
},
/// Encrypt a secret
Encrypt { plaintext: String },
/// Decrypt a secret
Decrypt { ciphertext: String },
/// Toggle browser allow-all runtime flag
BrowserAllowAll {
#[arg(long)]
enabled: bool,
},
/// Refresh model catalog
ModelsRefresh {
#[arg(long)]
provider: Option<String>,
#[arg(long, default_value_t = false)]
force: bool,
},
/// Migrate OpenClaw memory
MigrateOpenclaw {
#[arg(long)]
source_workspace: Option<String>,
#[arg(long, default_value_t = true)]
dry_run: bool,
},
}
#[derive(Debug, Subcommand)]
enum ConfigCommand {
/// Get full config snapshot
Get,
/// Update model settings with a JSON object
UpdateModel {
#[arg(long)]
json: String,
},
/// Update memory settings with a JSON object
UpdateMemory {
#[arg(long)]
json: String,
},
/// Update gateway settings with a JSON object
UpdateGateway {
#[arg(long)]
json: String,
},
/// Update runtime settings with a JSON object
UpdateRuntime {
#[arg(long)]
json: String,
},
/// Update browser settings with a JSON object
UpdateBrowser {
#[arg(long)]
json: String,
},
/// Replace tunnel settings with a JSON object
UpdateTunnel {
#[arg(long)]
json: String,
},
}
#[derive(Debug, Subcommand)]
enum ServiceCommand {
Install,
Start,
Stop,
Status,
Reinstall,
Uninstall,
}
#[derive(Debug, Subcommand)]
enum DoctorCommand {
/// Run doctor checks
Report,
/// Probe model catalog
Models {
#[arg(long)]
provider: Option<String>,
#[arg(long, default_value_t = true)]
use_cache: bool,
},
}
#[derive(Debug, Subcommand)]
enum IntegrationsCommand {
/// List integrations
List,
/// Get one integration info
Info {
#[arg(long)]
name: String,
},
}
#[derive(Debug, Subcommand)]
enum HardwareCommand {
/// Discover connected hardware
Discover,
/// Introspect one device path
Introspect {
#[arg(long)]
path: String,
},
}
#[derive(Debug, Args)]
struct AgentChatArgs {
message: String,
#[arg(long)]
provider: Option<String>,
#[arg(long)]
model: Option<String>,
#[arg(long)]
temperature: Option<f64>,
}
fn parse_json_arg(raw: &str) -> Result<serde_json::Value, String> {
serde_json::from_str(raw).map_err(|e| format!("invalid JSON for --json/--params: {e}"))
}
async fn call_local(method: &str, params: serde_json::Value) -> Result<serde_json::Value, String> {
openhuman_core::core_server::call_method(method, params).await
}
async fn execute(cli: Cli) -> Result<serde_json::Value, String> {
match cli.command {
Command::Serve { port } => openhuman_core::core_server::run_server(port)
.await
.map(|_| serde_json::Value::Null)
.map_err(|e| format!("serve failed: {e}")),
Command::Ping => call_local("core.ping", json!({})).await,
Command::Version => call_local("core.version", json!({})).await,
Command::Health => call_local("openhuman.health_snapshot", json!({})).await,
Command::RuntimeFlags => call_local("openhuman.get_runtime_flags", json!({})).await,
Command::SecurityPolicy => call_local("openhuman.security_policy_info", json!({})).await,
Command::Call { method, params } => call_local(&method, parse_json_arg(&params)?).await,
Command::Config { command } => match command {
ConfigCommand::Get => call_local("openhuman.get_config", json!({})).await,
ConfigCommand::UpdateModel { json } => {
call_local("openhuman.update_model_settings", parse_json_arg(&json)?).await
}
ConfigCommand::UpdateMemory { json } => {
call_local("openhuman.update_memory_settings", parse_json_arg(&json)?).await
}
ConfigCommand::UpdateGateway { json } => {
call_local("openhuman.update_gateway_settings", parse_json_arg(&json)?).await
}
ConfigCommand::UpdateRuntime { json } => {
call_local("openhuman.update_runtime_settings", parse_json_arg(&json)?).await
}
ConfigCommand::UpdateBrowser { json } => {
call_local("openhuman.update_browser_settings", parse_json_arg(&json)?).await
}
ConfigCommand::UpdateTunnel { json } => {
call_local("openhuman.update_tunnel_settings", parse_json_arg(&json)?).await
}
},
Command::Service { command } => match command {
ServiceCommand::Install => call_local("openhuman.service_install", json!({})).await,
ServiceCommand::Start => call_local("openhuman.service_start", json!({})).await,
ServiceCommand::Stop => call_local("openhuman.service_stop", json!({})).await,
ServiceCommand::Status => call_local("openhuman.service_status", json!({})).await,
ServiceCommand::Reinstall => {
call_local("openhuman.service_uninstall", json!({})).await?;
call_local("openhuman.service_install", json!({})).await
}
ServiceCommand::Uninstall => call_local("openhuman.service_uninstall", json!({})).await,
},
Command::Doctor { command } => match command {
DoctorCommand::Report => call_local("openhuman.doctor_report", json!({})).await,
DoctorCommand::Models {
provider,
use_cache,
} => {
call_local(
"openhuman.doctor_models",
json!({
"provider_override": provider,
"use_cache": use_cache,
}),
)
.await
}
},
Command::Integrations { command } => match command {
IntegrationsCommand::List => call_local("openhuman.list_integrations", json!({})).await,
IntegrationsCommand::Info { name } => {
call_local("openhuman.get_integration_info", json!({ "name": name })).await
}
},
Command::AgentChat(args) => {
call_local(
"openhuman.agent_chat",
json!({
"message": args.message,
"provider_override": args.provider,
"model_override": args.model,
"temperature": args.temperature,
}),
)
.await
}
Command::Hardware { command } => match command {
HardwareCommand::Discover => call_local("openhuman.hardware_discover", json!({})).await,
HardwareCommand::Introspect { path } => {
call_local("openhuman.hardware_introspect", json!({ "path": path })).await
}
},
Command::Encrypt { plaintext } => {
call_local(
"openhuman.encrypt_secret",
json!({ "plaintext": plaintext }),
)
.await
}
Command::Decrypt { ciphertext } => {
call_local(
"openhuman.decrypt_secret",
json!({ "ciphertext": ciphertext }),
)
.await
}
Command::BrowserAllowAll { enabled } => {
call_local(
"openhuman.set_browser_allow_all",
json!({ "enabled": enabled }),
)
.await
}
Command::ModelsRefresh { provider, force } => {
call_local(
"openhuman.models_refresh",
json!({
"provider_override": provider,
"force": force,
}),
)
.await
}
Command::MigrateOpenclaw {
source_workspace,
dry_run,
} => {
call_local(
"openhuman.migrate_openclaw",
json!({
"source_workspace": source_workspace,
"dry_run": dry_run,
}),
)
.await
}
}
}
#[tokio::main]
async fn main() {
let cli = Cli::parse();
match execute(cli).await {
Ok(value) => {
println!(
"{}",
serde_json::to_string_pretty(&value).unwrap_or_else(|_| "null".to_string())
);
}
Err(err) => {
eprintln!("{err}");
std::process::exit(1);
}
}
}
+14
View File
@@ -640,6 +640,20 @@ async fn dispatch(
}
}
pub async fn call_method(
method: &str,
params: serde_json::Value,
) -> Result<serde_json::Value, String> {
dispatch(
AppState {
core_version: env!("CARGO_PKG_VERSION").to_string(),
},
method,
params,
)
.await
}
async fn health_handler() -> impl IntoResponse {
(StatusCode::OK, Json(json!({ "ok": true })))
}
+7 -6
View File
@@ -16,13 +16,14 @@ fn windows_task_name() -> &'static str {
}
fn daemon_program_args(exe: &std::path::Path) -> Vec<String> {
let file_name = exe
.file_name()
.and_then(|n| n.to_str())
.unwrap_or_default()
.to_ascii_lowercase();
let raw_file_name = exe.file_name().and_then(|n| n.to_str()).unwrap_or_default();
let file_name = raw_file_name.to_ascii_lowercase();
let standalone_core_binary = file_name.contains("openhuman-core")
|| file_name.starts_with("openhuman-")
|| raw_file_name == "openhuman"
|| raw_file_name == "openhuman.exe";
if file_name.contains("openhuman-core") {
if standalone_core_binary {
vec!["serve".to_string()]
} else {
vec!["core".to_string(), "serve".to_string()]
+1 -1
View File
@@ -119,7 +119,7 @@ mod tests {
assert_eq!(result.unwrap(), "test");
}
#[tokio::test]
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_safe_async_execute_with_runtime() {
let result = safe_async_execute(async { "test".to_string() });
assert!(result.is_ok());
+2 -88
View File
@@ -1,90 +1,4 @@
#!/usr/bin/env bash
#
# Run E2E auth & access control tests only.
#
# Starts Appium, cleans app caches, runs the auth-access-control spec,
# then tears everything down. Each flow script is self-contained so
# specs don't pollute each other's Redux Persist state.
#
# Usage:
# ./scripts/e2e-auth.sh
# APPIUM_PORT=4723 ./scripts/e2e-auth.sh
#
# Run E2E auth & access control tests only. See scripts/e2e-run-spec.sh.
set -euo pipefail
APPIUM_PORT="${APPIUM_PORT:-4723}"
E2E_MOCK_PORT="${E2E_MOCK_PORT:-18473}"
SPEC="test/e2e/specs/auth-access-control.spec.ts"
export VITE_BACKEND_URL="http://127.0.0.1:${E2E_MOCK_PORT}"
export BACKEND_URL="http://127.0.0.1:${E2E_MOCK_PORT}"
# Clean cached app data for a fresh state — Redux Persist would otherwise
# remember the JWT from a previous run and skip the login flow.
echo "Cleaning cached app data..."
rm -rf ~/Library/WebKit/com.openhuman.app
rm -rf ~/Library/Caches/com.openhuman.app
rm -rf "$HOME/Library/Application Support/com.openhuman.app"
# Verify the frontend dist has the mock server URL baked in.
DIST_JS="$(ls dist/assets/index-*.js 2>/dev/null | head -1)"
if [ -z "$DIST_JS" ]; then
echo "ERROR: No frontend bundle found at dist/assets/index-*.js." >&2
echo " Run 'yarn test:e2e:build' to build the app before running E2E tests." >&2
exit 1
fi
if ! grep -q "127.0.0.1:${E2E_MOCK_PORT}" "$DIST_JS"; then
echo "ERROR: frontend bundle does NOT contain mock server URL (127.0.0.1:${E2E_MOCK_PORT})." >&2
echo " Run 'yarn test:e2e:build' to rebuild with the mock URL." >&2
exit 1
fi
echo "Verified: frontend bundle contains mock server URL."
# --- Resolve Node 24 via nvm ---------------------------------------------------
export NVM_DIR="${NVM_DIR:-$HOME/.nvm}"
# shellcheck source=/dev/null
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
NODE24="$(nvm which 24 2>/dev/null || true)"
if [ -z "$NODE24" ] || [ ! -x "$NODE24" ]; then
echo "ERROR: Node 24 is required for appium v3. Install it with: nvm install 24" >&2
exit 1
fi
APPIUM_BIN="$(dirname "$NODE24")/appium"
if [ ! -x "$APPIUM_BIN" ]; then
echo "ERROR: appium not found at $APPIUM_BIN. Install it with: nvm use 24 && npm i -g appium" >&2
exit 1
fi
# --- Start Appium in the background -------------------------------------------
APPIUM_LOG="/tmp/appium-e2e-auth.log"
NODE_VER=$("$NODE24" --version)
echo "Starting Appium on port $APPIUM_PORT (Node $NODE_VER)..."
echo " Appium logs: $APPIUM_LOG"
"$NODE24" "$APPIUM_BIN" --port "$APPIUM_PORT" --relaxed-security > "$APPIUM_LOG" 2>&1 &
APPIUM_PID=$!
cleanup() {
echo "Stopping Appium (pid $APPIUM_PID)..."
kill "$APPIUM_PID" 2>/dev/null || true
wait "$APPIUM_PID" 2>/dev/null || true
}
trap cleanup EXIT
# Wait for Appium to be ready
for i in $(seq 1 30); do
if curl -sf "http://127.0.0.1:$APPIUM_PORT/status" >/dev/null 2>&1; then
echo "Appium is ready."
break
fi
if [ "$i" -eq 30 ]; then
echo "ERROR: Appium did not start within 30 seconds." >&2
exit 1
fi
sleep 1
done
# --- Run WebDriverIO ----------------------------------------------------------
echo "Running E2E auth flow tests ($SPEC)..."
npx wdio run test/wdio.conf.ts --spec "$SPEC"
exec "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/e2e-run-spec.sh" "test/e2e/specs/auth-access-control.spec.ts" "auth"
+13 -7
View File
@@ -14,16 +14,22 @@ export VITE_BACKEND_URL="http://127.0.0.1:${E2E_MOCK_PORT:-18473}"
echo "Building E2E app bundle with VITE_BACKEND_URL=$VITE_BACKEND_URL"
# Clean Rust build cache so frontend assets get re-embedded
cargo clean --manifest-path src-tauri/Cargo.toml
if [ -z "${E2E_SKIP_CARGO_CLEAN:-}" ]; then
cargo clean --manifest-path src-tauri/Cargo.toml
else
echo "Skipping cargo clean (E2E_SKIP_CARGO_CLEAN is set)."
fi
# Load .env for other vars (Telegram API keys, etc.)
source scripts/load-dotenv.sh
if [ -f .env ]; then
# shellcheck source=/dev/null
source scripts/load-dotenv.sh
else
echo "No .env file — skipping load-dotenv (optional for CI)."
fi
# Re-export mock URL in case load-dotenv.sh clobbered it
export VITE_BACKEND_URL="http://127.0.0.1:${E2E_MOCK_PORT:-18473}"
# Build .app only (skip DMG to avoid bundle_dmg.sh failures)
tauri build --bundles app --debug
# Use npx so CI does not require a global Tauri CLI
npx tauri build --bundles app --debug
echo "E2E build complete."
+2 -88
View File
@@ -1,90 +1,4 @@
#!/usr/bin/env bash
#
# Run E2E cryptocurrency payment flow tests only.
#
# Starts Appium, cleans app caches, runs the crypto-payment-flow spec,
# then tears everything down. Each flow script is self-contained so
# specs don't pollute each other's Redux Persist state.
#
# Usage:
# ./scripts/e2e-crypto-payment.sh
# APPIUM_PORT=4723 ./scripts/e2e-crypto-payment.sh
#
# Run E2E crypto payment flow tests only. See scripts/e2e-run-spec.sh.
set -euo pipefail
APPIUM_PORT="${APPIUM_PORT:-4723}"
E2E_MOCK_PORT="${E2E_MOCK_PORT:-18473}"
SPEC="test/e2e/specs/crypto-payment-flow.spec.ts"
export VITE_BACKEND_URL="http://127.0.0.1:${E2E_MOCK_PORT}"
export BACKEND_URL="http://127.0.0.1:${E2E_MOCK_PORT}"
# Clean cached app data for a fresh state — Redux Persist would otherwise
# remember the JWT from a previous run and skip the login flow.
echo "Cleaning cached app data..."
rm -rf ~/Library/WebKit/com.openhuman.app
rm -rf ~/Library/Caches/com.openhuman.app
rm -rf "$HOME/Library/Application Support/com.openhuman.app"
# Verify the frontend dist has the mock server URL baked in.
DIST_JS="$(ls dist/assets/index-*.js 2>/dev/null | head -1)"
if [ -z "$DIST_JS" ]; then
echo "ERROR: No frontend bundle found at dist/assets/index-*.js." >&2
echo " Run 'yarn test:e2e:build' to build the app before running E2E tests." >&2
exit 1
fi
if ! grep -q "127.0.0.1:${E2E_MOCK_PORT}" "$DIST_JS"; then
echo "ERROR: frontend bundle does NOT contain mock server URL (127.0.0.1:${E2E_MOCK_PORT})." >&2
echo " Run 'yarn test:e2e:build' to rebuild with the mock URL." >&2
exit 1
fi
echo "Verified: frontend bundle contains mock server URL."
# --- Resolve Node 24 via nvm ---------------------------------------------------
export NVM_DIR="${NVM_DIR:-$HOME/.nvm}"
# shellcheck source=/dev/null
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
NODE24="$(nvm which 24 2>/dev/null || true)"
if [ -z "$NODE24" ] || [ ! -x "$NODE24" ]; then
echo "ERROR: Node 24 is required for appium v3. Install it with: nvm install 24" >&2
exit 1
fi
APPIUM_BIN="$(dirname "$NODE24")/appium"
if [ ! -x "$APPIUM_BIN" ]; then
echo "ERROR: appium not found at $APPIUM_BIN. Install it with: nvm use 24 && npm i -g appium" >&2
exit 1
fi
# --- Start Appium in the background -------------------------------------------
APPIUM_LOG="/tmp/appium-e2e-crypto-payment.log"
NODE_VER=$("$NODE24" --version)
echo "Starting Appium on port $APPIUM_PORT (Node $NODE_VER)..."
echo " Appium logs: $APPIUM_LOG"
"$NODE24" "$APPIUM_BIN" --port "$APPIUM_PORT" --relaxed-security > "$APPIUM_LOG" 2>&1 &
APPIUM_PID=$!
cleanup() {
echo "Stopping Appium (pid $APPIUM_PID)..."
kill "$APPIUM_PID" 2>/dev/null || true
wait "$APPIUM_PID" 2>/dev/null || true
}
trap cleanup EXIT
# Wait for Appium to be ready
for i in $(seq 1 30); do
if curl -sf "http://127.0.0.1:$APPIUM_PORT/status" >/dev/null 2>&1; then
echo "Appium is ready."
break
fi
if [ "$i" -eq 30 ]; then
echo "ERROR: Appium did not start within 30 seconds." >&2
exit 1
fi
sleep 1
done
# --- Run WebDriverIO ----------------------------------------------------------
echo "Running E2E crypto payment flow tests ($SPEC)..."
npx wdio run test/wdio.conf.ts --spec "$SPEC"
exec "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/e2e-run-spec.sh" "test/e2e/specs/crypto-payment-flow.spec.ts" "crypto-payment"
+2 -88
View File
@@ -1,90 +1,4 @@
#!/usr/bin/env bash
#
# Run E2E Gmail integration flow tests only.
#
# Starts Appium, cleans app caches, runs the gmail-flow spec,
# then tears everything down. Each flow script is self-contained so
# specs don't pollute each other's Redux Persist state.
#
# Usage:
# ./scripts/e2e-gmail.sh
# APPIUM_PORT=4723 ./scripts/e2e-gmail.sh
#
# Run E2E Gmail integration flow tests only. See scripts/e2e-run-spec.sh.
set -euo pipefail
APPIUM_PORT="${APPIUM_PORT:-4723}"
E2E_MOCK_PORT="${E2E_MOCK_PORT:-18473}"
SPEC="test/e2e/specs/gmail-flow.spec.ts"
export VITE_BACKEND_URL="http://127.0.0.1:${E2E_MOCK_PORT}"
export BACKEND_URL="http://127.0.0.1:${E2E_MOCK_PORT}"
# Clean cached app data for a fresh state — Redux Persist would otherwise
# remember the JWT from a previous run and skip the login flow.
echo "Cleaning cached app data..."
rm -rf ~/Library/WebKit/com.openhuman.app
rm -rf ~/Library/Caches/com.openhuman.app
rm -rf "$HOME/Library/Application Support/com.openhuman.app"
# Verify the frontend dist has the mock server URL baked in.
DIST_JS="$(ls dist/assets/index-*.js 2>/dev/null | head -1)"
if [ -z "$DIST_JS" ]; then
echo "ERROR: No frontend bundle found at dist/assets/index-*.js." >&2
echo " Run 'yarn test:e2e:build' to build the app before running E2E tests." >&2
exit 1
fi
if ! grep -q "127.0.0.1:${E2E_MOCK_PORT}" "$DIST_JS"; then
echo "ERROR: frontend bundle does NOT contain mock server URL (127.0.0.1:${E2E_MOCK_PORT})." >&2
echo " Run 'yarn test:e2e:build' to rebuild with the mock URL." >&2
exit 1
fi
echo "Verified: frontend bundle contains mock server URL."
# --- Resolve Node 24 via nvm ---------------------------------------------------
export NVM_DIR="${NVM_DIR:-$HOME/.nvm}"
# shellcheck source=/dev/null
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
NODE24="$(nvm which 24 2>/dev/null || true)"
if [ -z "$NODE24" ] || [ ! -x "$NODE24" ]; then
echo "ERROR: Node 24 is required for appium v3. Install it with: nvm install 24" >&2
exit 1
fi
APPIUM_BIN="$(dirname "$NODE24")/appium"
if [ ! -x "$APPIUM_BIN" ]; then
echo "ERROR: appium not found at $APPIUM_BIN. Install it with: nvm use 24 && npm i -g appium" >&2
exit 1
fi
# --- Start Appium in the background -------------------------------------------
APPIUM_LOG="/tmp/appium-e2e-gmail.log"
NODE_VER=$("$NODE24" --version)
echo "Starting Appium on port $APPIUM_PORT (Node $NODE_VER)..."
echo " Appium logs: $APPIUM_LOG"
"$NODE24" "$APPIUM_BIN" --port "$APPIUM_PORT" --relaxed-security > "$APPIUM_LOG" 2>&1 &
APPIUM_PID=$!
cleanup() {
echo "Stopping Appium (pid $APPIUM_PID)..."
kill "$APPIUM_PID" 2>/dev/null || true
wait "$APPIUM_PID" 2>/dev/null || true
}
trap cleanup EXIT
# Wait for Appium to be ready
for i in $(seq 1 30); do
if curl -sf "http://127.0.0.1:$APPIUM_PORT/status" >/dev/null 2>&1; then
echo "Appium is ready."
break
fi
if [ "$i" -eq 30 ]; then
echo "ERROR: Appium did not start within 30 seconds." >&2
exit 1
fi
sleep 1
done
# --- Run WebDriverIO ----------------------------------------------------------
echo "Running E2E Gmail integration flow tests ($SPEC)..."
npx wdio run test/wdio.conf.ts --spec "$SPEC"
exec "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/e2e-run-spec.sh" "test/e2e/specs/gmail-flow.spec.ts" "gmail"
+2 -91
View File
@@ -1,93 +1,4 @@
#!/usr/bin/env bash
#
# Run E2E login flow tests only.
#
# Starts Appium, cleans app caches, runs the login-flow spec,
# then tears everything down. Each flow script is self-contained so
# specs don't pollute each other's Redux Persist state.
#
# Usage:
# ./scripts/e2e-login.sh
# APPIUM_PORT=4723 ./scripts/e2e-login.sh
#
# Run E2E login flow tests only. See scripts/e2e-run-spec.sh.
set -euo pipefail
APPIUM_PORT="${APPIUM_PORT:-4723}"
E2E_MOCK_PORT="${E2E_MOCK_PORT:-18473}"
SPEC="test/e2e/specs/login-flow.spec.ts"
# Point the app at the local mock server (baked into the build already, but
# also exported here so the Rust backend / any env-based config picks it up).
export VITE_BACKEND_URL="http://127.0.0.1:${E2E_MOCK_PORT}"
export BACKEND_URL="http://127.0.0.1:${E2E_MOCK_PORT}"
# Clean cached app data for a fresh state — Redux Persist would otherwise
# remember the JWT from a previous run and skip the login flow.
echo "Cleaning cached app data..."
rm -rf ~/Library/WebKit/com.openhuman.app
rm -rf ~/Library/Caches/com.openhuman.app
rm -rf "$HOME/Library/Application Support/com.openhuman.app"
# Verify the frontend dist has the mock server URL baked in (not production).
# Tauri compresses assets when embedding, so we check dist/ not the binary.
DIST_JS="$(ls dist/assets/index-*.js 2>/dev/null | head -1)"
if [ -z "$DIST_JS" ]; then
echo "ERROR: No frontend bundle found at dist/assets/index-*.js." >&2
echo " Run 'yarn test:e2e:build' to build the app before running E2E tests." >&2
exit 1
fi
if ! grep -q "127.0.0.1:${E2E_MOCK_PORT}" "$DIST_JS"; then
echo "ERROR: frontend bundle does NOT contain mock server URL (127.0.0.1:${E2E_MOCK_PORT})." >&2
echo " Run 'yarn test:e2e:build' to rebuild with the mock URL." >&2
exit 1
fi
echo "Verified: frontend bundle contains mock server URL."
# --- Resolve Node 24 via nvm ---------------------------------------------------
export NVM_DIR="${NVM_DIR:-$HOME/.nvm}"
# shellcheck source=/dev/null
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
NODE24="$(nvm which 24 2>/dev/null || true)"
if [ -z "$NODE24" ] || [ ! -x "$NODE24" ]; then
echo "ERROR: Node 24 is required for appium v3. Install it with: nvm install 24" >&2
exit 1
fi
APPIUM_BIN="$(dirname "$NODE24")/appium"
if [ ! -x "$APPIUM_BIN" ]; then
echo "ERROR: appium not found at $APPIUM_BIN. Install it with: nvm use 24 && npm i -g appium" >&2
exit 1
fi
# --- Start Appium in the background -------------------------------------------
APPIUM_LOG="/tmp/appium-e2e-login.log"
NODE_VER=$("$NODE24" --version)
echo "Starting Appium on port $APPIUM_PORT (Node $NODE_VER)..."
echo " Appium logs: $APPIUM_LOG"
"$NODE24" "$APPIUM_BIN" --port "$APPIUM_PORT" --relaxed-security > "$APPIUM_LOG" 2>&1 &
APPIUM_PID=$!
cleanup() {
echo "Stopping Appium (pid $APPIUM_PID)..."
kill "$APPIUM_PID" 2>/dev/null || true
wait "$APPIUM_PID" 2>/dev/null || true
}
trap cleanup EXIT
# Wait for Appium to be ready
for i in $(seq 1 30); do
if curl -sf "http://127.0.0.1:$APPIUM_PORT/status" >/dev/null 2>&1; then
echo "Appium is ready."
break
fi
if [ "$i" -eq 30 ]; then
echo "ERROR: Appium did not start within 30 seconds." >&2
exit 1
fi
sleep 1
done
# --- Run WebDriverIO ----------------------------------------------------------
echo "Running E2E login flow tests ($SPEC)..."
npx wdio run test/wdio.conf.ts --spec "$SPEC"
exec "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/e2e-run-spec.sh" "test/e2e/specs/login-flow.spec.ts" "login"
+2 -88
View File
@@ -1,90 +1,4 @@
#!/usr/bin/env bash
#
# Run E2E Notion integration flow tests only.
#
# Starts Appium, cleans app caches, runs the notion-flow spec,
# then tears everything down. Each flow script is self-contained so
# specs don't pollute each other's Redux Persist state.
#
# Usage:
# ./scripts/e2e-notion.sh
# APPIUM_PORT=4723 ./scripts/e2e-notion.sh
#
# Run E2E Notion integration flow tests only. See scripts/e2e-run-spec.sh.
set -euo pipefail
APPIUM_PORT="${APPIUM_PORT:-4723}"
E2E_MOCK_PORT="${E2E_MOCK_PORT:-18473}"
SPEC="test/e2e/specs/notion-flow.spec.ts"
export VITE_BACKEND_URL="http://127.0.0.1:${E2E_MOCK_PORT}"
export BACKEND_URL="http://127.0.0.1:${E2E_MOCK_PORT}"
# Clean cached app data for a fresh state — Redux Persist would otherwise
# remember the JWT from a previous run and skip the login flow.
echo "Cleaning cached app data..."
rm -rf ~/Library/WebKit/com.openhuman.app
rm -rf ~/Library/Caches/com.openhuman.app
rm -rf "$HOME/Library/Application Support/com.openhuman.app"
# Verify the frontend dist has the mock server URL baked in.
DIST_JS="$(ls dist/assets/index-*.js 2>/dev/null | head -1)"
if [ -z "$DIST_JS" ]; then
echo "ERROR: No frontend bundle found at dist/assets/index-*.js." >&2
echo " Run 'yarn test:e2e:build' to build the app before running E2E tests." >&2
exit 1
fi
if ! grep -q "127.0.0.1:${E2E_MOCK_PORT}" "$DIST_JS"; then
echo "ERROR: frontend bundle does NOT contain mock server URL (127.0.0.1:${E2E_MOCK_PORT})." >&2
echo " Run 'yarn test:e2e:build' to rebuild with the mock URL." >&2
exit 1
fi
echo "Verified: frontend bundle contains mock server URL."
# --- Resolve Node 24 via nvm ---------------------------------------------------
export NVM_DIR="${NVM_DIR:-$HOME/.nvm}"
# shellcheck source=/dev/null
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
NODE24="$(nvm which 24 2>/dev/null || true)"
if [ -z "$NODE24" ] || [ ! -x "$NODE24" ]; then
echo "ERROR: Node 24 is required for appium v3. Install it with: nvm install 24" >&2
exit 1
fi
APPIUM_BIN="$(dirname "$NODE24")/appium"
if [ ! -x "$APPIUM_BIN" ]; then
echo "ERROR: appium not found at $APPIUM_BIN. Install it with: nvm use 24 && npm i -g appium" >&2
exit 1
fi
# --- Start Appium in the background -------------------------------------------
APPIUM_LOG="/tmp/appium-e2e-notion.log"
NODE_VER=$("$NODE24" --version)
echo "Starting Appium on port $APPIUM_PORT (Node $NODE_VER)..."
echo " Appium logs: $APPIUM_LOG"
"$NODE24" "$APPIUM_BIN" --port "$APPIUM_PORT" --relaxed-security > "$APPIUM_LOG" 2>&1 &
APPIUM_PID=$!
cleanup() {
echo "Stopping Appium (pid $APPIUM_PID)..."
kill "$APPIUM_PID" 2>/dev/null || true
wait "$APPIUM_PID" 2>/dev/null || true
}
trap cleanup EXIT
# Wait for Appium to be ready
for i in $(seq 1 30); do
if curl -sf "http://127.0.0.1:$APPIUM_PORT/status" >/dev/null 2>&1; then
echo "Appium is ready."
break
fi
if [ "$i" -eq 30 ]; then
echo "ERROR: Appium did not start within 30 seconds." >&2
exit 1
fi
sleep 1
done
# --- Run WebDriverIO ----------------------------------------------------------
echo "Running E2E Notion integration flow tests ($SPEC)..."
npx wdio run test/wdio.conf.ts --spec "$SPEC"
exec "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/e2e-run-spec.sh" "test/e2e/specs/notion-flow.spec.ts" "notion"
+2 -88
View File
@@ -1,90 +1,4 @@
#!/usr/bin/env bash
#
# Run E2E card payment flow tests only.
#
# Starts Appium, cleans app caches, runs the card-payment-flow spec,
# then tears everything down. Each flow script is self-contained so
# specs don't pollute each other's Redux Persist state.
#
# Usage:
# ./scripts/e2e-payment.sh
# APPIUM_PORT=4723 ./scripts/e2e-payment.sh
#
# Run E2E card payment flow tests only. See scripts/e2e-run-spec.sh.
set -euo pipefail
APPIUM_PORT="${APPIUM_PORT:-4723}"
E2E_MOCK_PORT="${E2E_MOCK_PORT:-18473}"
SPEC="test/e2e/specs/card-payment-flow.spec.ts"
export VITE_BACKEND_URL="http://127.0.0.1:${E2E_MOCK_PORT}"
export BACKEND_URL="http://127.0.0.1:${E2E_MOCK_PORT}"
# Clean cached app data for a fresh state — Redux Persist would otherwise
# remember the JWT from a previous run and skip the login flow.
echo "Cleaning cached app data..."
rm -rf ~/Library/WebKit/com.openhuman.app
rm -rf ~/Library/Caches/com.openhuman.app
rm -rf "$HOME/Library/Application Support/com.openhuman.app"
# Verify the frontend dist has the mock server URL baked in.
DIST_JS="$(ls dist/assets/index-*.js 2>/dev/null | head -1)"
if [ -z "$DIST_JS" ]; then
echo "ERROR: No frontend bundle found at dist/assets/index-*.js." >&2
echo " Run 'yarn test:e2e:build' to build the app before running E2E tests." >&2
exit 1
fi
if ! grep -q "127.0.0.1:${E2E_MOCK_PORT}" "$DIST_JS"; then
echo "ERROR: frontend bundle does NOT contain mock server URL (127.0.0.1:${E2E_MOCK_PORT})." >&2
echo " Run 'yarn test:e2e:build' to rebuild with the mock URL." >&2
exit 1
fi
echo "Verified: frontend bundle contains mock server URL."
# --- Resolve Node 24 via nvm ---------------------------------------------------
export NVM_DIR="${NVM_DIR:-$HOME/.nvm}"
# shellcheck source=/dev/null
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
NODE24="$(nvm which 24 2>/dev/null || true)"
if [ -z "$NODE24" ] || [ ! -x "$NODE24" ]; then
echo "ERROR: Node 24 is required for appium v3. Install it with: nvm install 24" >&2
exit 1
fi
APPIUM_BIN="$(dirname "$NODE24")/appium"
if [ ! -x "$APPIUM_BIN" ]; then
echo "ERROR: appium not found at $APPIUM_BIN. Install it with: nvm use 24 && npm i -g appium" >&2
exit 1
fi
# --- Start Appium in the background -------------------------------------------
APPIUM_LOG="/tmp/appium-e2e-payment.log"
NODE_VER=$("$NODE24" --version)
echo "Starting Appium on port $APPIUM_PORT (Node $NODE_VER)..."
echo " Appium logs: $APPIUM_LOG"
"$NODE24" "$APPIUM_BIN" --port "$APPIUM_PORT" --relaxed-security > "$APPIUM_LOG" 2>&1 &
APPIUM_PID=$!
cleanup() {
echo "Stopping Appium (pid $APPIUM_PID)..."
kill "$APPIUM_PID" 2>/dev/null || true
wait "$APPIUM_PID" 2>/dev/null || true
}
trap cleanup EXIT
# Wait for Appium to be ready
for i in $(seq 1 30); do
if curl -sf "http://127.0.0.1:$APPIUM_PORT/status" >/dev/null 2>&1; then
echo "Appium is ready."
break
fi
if [ "$i" -eq 30 ]; then
echo "ERROR: Appium did not start within 30 seconds." >&2
exit 1
fi
sleep 1
done
# --- Run WebDriverIO ----------------------------------------------------------
echo "Running E2E card payment flow tests ($SPEC)..."
npx wdio run test/wdio.conf.ts --spec "$SPEC"
exec "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/e2e-run-spec.sh" "test/e2e/specs/card-payment-flow.spec.ts" "card-payment"
+38
View File
@@ -0,0 +1,38 @@
#!/usr/bin/env bash
# Resolve Node 24+ and Appium for E2E scripts (local nvm or CI PATH).
# shellcheck disable=SC2034
# Outputs: NODE24, APPIUM_BIN (export for callers)
NODE24="$(command -v node 2>/dev/null || true)"
export NVM_DIR="${NVM_DIR:-$HOME/.nvm}"
if [ -s "$NVM_DIR/nvm.sh" ]; then
# shellcheck source=/dev/null
. "$NVM_DIR/nvm.sh"
NVM_NODE="$(nvm which 24 2>/dev/null || true)"
if [ -n "${NVM_NODE:-}" ] && [ -x "$NVM_NODE" ]; then
NODE24="$NVM_NODE"
fi
fi
if [ -z "${NODE24:-}" ] || [ ! -x "$NODE24" ]; then
echo "ERROR: Node.js is required (Node 24+ for Appium v3)." >&2
exit 1
fi
NODE_MAJOR="$("$NODE24" --version | sed 's/^v//' | cut -d. -f1)"
if [ "${NODE_MAJOR:-0}" -lt 24 ]; then
echo "ERROR: Node 24+ is required for Appium v3 (found $($NODE24 --version))." >&2
exit 1
fi
APPIUM_BIN="$(command -v appium 2>/dev/null || true)"
if [ -z "${APPIUM_BIN:-}" ] || [ ! -x "$APPIUM_BIN" ]; then
APPIUM_BIN="$(dirname "$NODE24")/appium"
fi
if [ ! -x "$APPIUM_BIN" ]; then
echo "ERROR: appium not found. Install with: npm install -g appium" >&2
exit 1
fi
export NODE24
export APPIUM_BIN
+26
View File
@@ -0,0 +1,26 @@
#!/usr/bin/env bash
#
# Run all E2E WDIO specs sequentially (Appium restarted per spec).
# Requires a prior E2E app build: yarn test:e2e:build
#
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$ROOT"
run() {
"$ROOT/scripts/e2e-run-spec.sh" "$1" "$2"
}
run "test/e2e/specs/login-flow.spec.ts" "login"
run "test/e2e/specs/auth-access-control.spec.ts" "auth"
run "test/e2e/specs/telegram-flow.spec.ts" "telegram"
run "test/e2e/specs/gmail-flow.spec.ts" "gmail"
run "test/e2e/specs/notion-flow.spec.ts" "notion"
run "test/e2e/specs/card-payment-flow.spec.ts" "card-payment"
run "test/e2e/specs/crypto-payment-flow.spec.ts" "crypto-payment"
run "test/e2e/specs/navigation.spec.ts" "navigation"
run "test/e2e/specs/smoke.spec.ts" "smoke"
run "test/e2e/specs/tauri-commands.spec.ts" "tauri-commands"
echo "All E2E flows completed."
+68
View File
@@ -0,0 +1,68 @@
#!/usr/bin/env bash
#
# Run a single WebDriverIO E2E spec (Appium mac2 + mock server in spec).
#
# Usage:
# ./scripts/e2e-run-spec.sh test/e2e/specs/login-flow.spec.ts [log-suffix]
#
set -euo pipefail
SPEC="${1:?spec path required}"
LOG_SUFFIX="${2:-$(basename "$SPEC" .spec.ts)}"
APPIUM_PORT="${APPIUM_PORT:-4723}"
E2E_MOCK_PORT="${E2E_MOCK_PORT:-18473}"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=/dev/null
source "$SCRIPT_DIR/e2e-resolve-node-appium.sh"
export VITE_BACKEND_URL="http://127.0.0.1:${E2E_MOCK_PORT}"
export BACKEND_URL="http://127.0.0.1:${E2E_MOCK_PORT}"
echo "Cleaning cached app data..."
rm -rf ~/Library/WebKit/com.openhuman.app
rm -rf ~/Library/Caches/com.openhuman.app
rm -rf "$HOME/Library/Application Support/com.openhuman.app"
DIST_JS="$(ls dist/assets/index-*.js 2>/dev/null | head -1)"
if [ -z "$DIST_JS" ]; then
echo "ERROR: No frontend bundle found at dist/assets/index-*.js." >&2
echo " Run 'yarn test:e2e:build' to build the app before running E2E tests." >&2
exit 1
fi
if ! grep -q "127.0.0.1:${E2E_MOCK_PORT}" "$DIST_JS"; then
echo "ERROR: frontend bundle does NOT contain mock server URL (127.0.0.1:${E2E_MOCK_PORT})." >&2
echo " Run 'yarn test:e2e:build' to rebuild with the mock URL." >&2
exit 1
fi
echo "Verified: frontend bundle contains mock server URL."
APPIUM_LOG="/tmp/appium-e2e-${LOG_SUFFIX}.log"
NODE_VER=$("$NODE24" --version)
echo "Starting Appium on port $APPIUM_PORT (Node $NODE_VER)..."
echo " Appium logs: $APPIUM_LOG"
"$APPIUM_BIN" --port "$APPIUM_PORT" --relaxed-security > "$APPIUM_LOG" 2>&1 &
APPIUM_PID=$!
cleanup() {
echo "Stopping Appium (pid $APPIUM_PID)..."
kill "$APPIUM_PID" 2>/dev/null || true
wait "$APPIUM_PID" 2>/dev/null || true
}
trap cleanup EXIT
for i in $(seq 1 30); do
if curl -sf "http://127.0.0.1:$APPIUM_PORT/status" >/dev/null 2>&1; then
echo "Appium is ready."
break
fi
if [ "$i" -eq 30 ]; then
echo "ERROR: Appium did not start within 30 seconds." >&2
exit 1
fi
sleep 1
done
echo "Running E2E spec ($SPEC)..."
npx wdio run test/wdio.conf.ts --spec "$SPEC"
+2 -88
View File
@@ -1,90 +1,4 @@
#!/usr/bin/env bash
#
# Run E2E Telegram integration flow tests only.
#
# Starts Appium, cleans app caches, runs the telegram-flow spec,
# then tears everything down. Each flow script is self-contained so
# specs don't pollute each other's Redux Persist state.
#
# Usage:
# ./scripts/e2e-telegram.sh
# APPIUM_PORT=4723 ./scripts/e2e-telegram.sh
#
# Run E2E Telegram integration flow tests only. See scripts/e2e-run-spec.sh.
set -euo pipefail
APPIUM_PORT="${APPIUM_PORT:-4723}"
E2E_MOCK_PORT="${E2E_MOCK_PORT:-18473}"
SPEC="test/e2e/specs/telegram-flow.spec.ts"
export VITE_BACKEND_URL="http://127.0.0.1:${E2E_MOCK_PORT}"
export BACKEND_URL="http://127.0.0.1:${E2E_MOCK_PORT}"
# Clean cached app data for a fresh state — Redux Persist would otherwise
# remember the JWT from a previous run and skip the login flow.
echo "Cleaning cached app data..."
rm -rf ~/Library/WebKit/com.openhuman.app
rm -rf ~/Library/Caches/com.openhuman.app
rm -rf "$HOME/Library/Application Support/com.openhuman.app"
# Verify the frontend dist has the mock server URL baked in.
DIST_JS="$(ls dist/assets/index-*.js 2>/dev/null | head -1)"
if [ -z "$DIST_JS" ]; then
echo "ERROR: No frontend bundle found at dist/assets/index-*.js." >&2
echo " Run 'yarn test:e2e:build' to build the app before running E2E tests." >&2
exit 1
fi
if ! grep -q "127.0.0.1:${E2E_MOCK_PORT}" "$DIST_JS"; then
echo "ERROR: frontend bundle does NOT contain mock server URL (127.0.0.1:${E2E_MOCK_PORT})." >&2
echo " Run 'yarn test:e2e:build' to rebuild with the mock URL." >&2
exit 1
fi
echo "Verified: frontend bundle contains mock server URL."
# --- Resolve Node 24 via nvm ---------------------------------------------------
export NVM_DIR="${NVM_DIR:-$HOME/.nvm}"
# shellcheck source=/dev/null
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
NODE24="$(nvm which 24 2>/dev/null || true)"
if [ -z "$NODE24" ] || [ ! -x "$NODE24" ]; then
echo "ERROR: Node 24 is required for appium v3. Install it with: nvm install 24" >&2
exit 1
fi
APPIUM_BIN="$(dirname "$NODE24")/appium"
if [ ! -x "$APPIUM_BIN" ]; then
echo "ERROR: appium not found at $APPIUM_BIN. Install it with: nvm use 24 && npm i -g appium" >&2
exit 1
fi
# --- Start Appium in the background -------------------------------------------
APPIUM_LOG="/tmp/appium-e2e-telegram.log"
NODE_VER=$("$NODE24" --version)
echo "Starting Appium on port $APPIUM_PORT (Node $NODE_VER)..."
echo " Appium logs: $APPIUM_LOG"
"$NODE24" "$APPIUM_BIN" --port "$APPIUM_PORT" --relaxed-security > "$APPIUM_LOG" 2>&1 &
APPIUM_PID=$!
cleanup() {
echo "Stopping Appium (pid $APPIUM_PID)..."
kill "$APPIUM_PID" 2>/dev/null || true
wait "$APPIUM_PID" 2>/dev/null || true
}
trap cleanup EXIT
# Wait for Appium to be ready
for i in $(seq 1 30); do
if curl -sf "http://127.0.0.1:$APPIUM_PORT/status" >/dev/null 2>&1; then
echo "Appium is ready."
break
fi
if [ "$i" -eq 30 ]; then
echo "ERROR: Appium did not start within 30 seconds." >&2
exit 1
fi
sleep 1
done
# --- Run WebDriverIO ----------------------------------------------------------
echo "Running E2E Telegram integration flow tests ($SPEC)..."
npx wdio run test/wdio.conf.ts --spec "$SPEC"
exec "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/e2e-run-spec.sh" "test/e2e/specs/telegram-flow.spec.ts" "telegram"
+1 -1
Submodule skills updated: 4ef9e9b368...e856b8e756
+17 -6
View File
@@ -117,16 +117,25 @@ pub fn default_core_bin() -> Option<PathBuf> {
let exe_dir = exe.parent()?;
#[cfg(windows)]
let standalone = exe_dir.join("openhuman-core.exe");
let standalone = exe_dir.join("openhuman.exe");
#[cfg(not(windows))]
let standalone = exe_dir.join("openhuman-core");
let standalone = exe_dir.join("openhuman");
if standalone.exists() {
return Some(standalone);
}
// Sidecar layout: bundle.externalBin("binaries/openhuman-core") is emitted as
// openhuman-core-<target-triple>(.exe) under app resources.
#[cfg(windows)]
let legacy_standalone = exe_dir.join("openhuman-core.exe");
#[cfg(not(windows))]
let legacy_standalone = exe_dir.join("openhuman-core");
if legacy_standalone.exists() {
return Some(legacy_standalone);
}
// Sidecar layout: bundle.externalBin("binaries/openhuman") is emitted as
// openhuman-<target-triple>(.exe) under app resources.
let mut search_dirs = vec![exe_dir.to_path_buf()];
#[cfg(target_os = "macos")]
{
@@ -149,9 +158,11 @@ pub fn default_core_bin() -> Option<PathBuf> {
};
#[cfg(windows)]
let matches = file_name.starts_with("openhuman-core-") && file_name.ends_with(".exe");
let matches = (file_name.starts_with("openhuman-") && file_name.ends_with(".exe"))
|| (file_name.starts_with("openhuman-core-") && file_name.ends_with(".exe"));
#[cfg(not(windows))]
let matches = file_name.starts_with("openhuman-core-");
let matches =
file_name.starts_with("openhuman-") || file_name.starts_with("openhuman-core-");
if matches {
return Some(path);
+1 -1
View File
@@ -42,7 +42,7 @@
"../rust-core/ai"
],
"externalBin": [
"binaries/openhuman-core"
"binaries/openhuman"
],
"createUpdaterArtifacts": true,
"macOS": {
+1 -1
View File
@@ -9,6 +9,6 @@
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
<script type="module" src="/main.tsx"></script>
</body>
</html>
+1 -1
View File
@@ -1,5 +1,5 @@
/* eslint-disable @typescript-eslint/no-explicit-any -- intentional global polyfill assignments */
// Polyfill Node.js globals for browser (required by telegram package)
// Polyfill Node.js globals for browser dependencies
// This must be imported FIRST before any other imports that use Node.js APIs
import * as util from 'util';
import { Buffer } from 'buffer';
-2
View File
@@ -30,8 +30,6 @@ vi.mock('@tauri-apps/plugin-opener', () => ({ open: vi.fn() }));
vi.mock('@tauri-apps/plugin-os', () => ({ platform: vi.fn().mockResolvedValue('macos') }));
vi.mock('@tauri-apps/plugin-shell', () => ({ Command: vi.fn(), open: vi.fn() }));
// Mock tauriCommands to prevent Tauri API calls in tests
vi.mock('../utils/tauriCommands', () => ({
isTauri: () => false,
+1 -1
View File
@@ -2,7 +2,7 @@
module.exports = {
darkMode: 'class',
content: [
"./index.html",
"./src/index.html",
"./src/**/*.{js,ts,jsx,tsx}",
],
theme: {
+7 -1
View File
@@ -7,6 +7,12 @@ const host = process.env.TAURI_DEV_HOST;
// https://vite.dev/config/
export default defineConfig(async () => ({
root: "src",
publicDir: "../public",
build: {
outDir: "../dist",
emptyOutDir: true,
},
plugins: [
nodePolyfills({
include: ["buffer", "process", "util", "os", "crypto", "stream"],
@@ -52,6 +58,6 @@ export default defineConfig(async () => ({
},
},
optimizeDeps: {
include: ["buffer", "process", "util", "os-browserify", "telegram"],
include: ["buffer", "process", "util", "os-browserify"],
},
}));
+6 -306
View File
@@ -281,11 +281,6 @@
resolved "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz"
integrity sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==
"@cryptography/aes@^0.1.1":
version "0.1.1"
resolved "https://registry.npmjs.org/@cryptography/aes/-/aes-0.1.1.tgz"
integrity sha512-PcYz4FDGblO6tM2kSC+VzhhK62vml6k6/YAkiWtyPvrgJVfnDRoHGDtKn5UiaRRUrvUTTocBpvc2rRgTCqxjsg==
"@csstools/color-helpers@^6.0.1":
version "6.0.1"
resolved "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.0.1.tgz"
@@ -1402,13 +1397,6 @@
dependencies:
"@tauri-apps/api" "^2.8.0"
"@tauri-apps/plugin-shell@~2":
version "2.3.4"
resolved "https://registry.npmjs.org/@tauri-apps/plugin-shell/-/plugin-shell-2.3.4.tgz"
integrity sha512-ktsRWf8wHLD17aZEyqE8c5x98eNAuTizR1FSX475zQ4TxaiJnhwksLygQz+AGwckJL5bfEP13nWrlTNQJUpKpA==
dependencies:
"@tauri-apps/api" "^2.8.0"
"@testing-library/dom@^10.4.1":
version "10.4.1"
resolved "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz"
@@ -1442,11 +1430,6 @@
dependencies:
"@babel/runtime" "^7.12.5"
"@testing-library/user-event@^14.6.1":
version "14.6.1"
resolved "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.1.tgz"
integrity sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==
"@tootallnate/quickjs-emscripten@^0.23.0":
version "0.23.0"
resolved "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz"
@@ -1548,11 +1531,6 @@
dependencies:
"@types/unist" "*"
"@types/history@^4.7.11":
version "4.7.11"
resolved "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz"
integrity sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==
"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.6":
version "2.0.6"
resolved "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz"
@@ -1630,24 +1608,7 @@
resolved "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz"
integrity sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==
"@types/react-router-dom@^5.3.3":
version "5.3.3"
resolved "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.3.3.tgz"
integrity sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==
dependencies:
"@types/history" "^4.7.11"
"@types/react" "*"
"@types/react-router" "*"
"@types/react-router@*":
version "5.1.20"
resolved "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.20.tgz"
integrity sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==
dependencies:
"@types/history" "^4.7.11"
"@types/react" "*"
"@types/react@*", "@types/react@^19.1.8":
"@types/react@^19.1.8":
version "19.2.9"
resolved "https://registry.npmjs.org/@types/react/-/react-19.2.9.tgz"
integrity sha512-Lpo8kgb/igvMIPeNV2rsYKTgaORYdO1XGVZ4Qz3akwOj0ySGYMPlQWa8BaLn0G63D1aSaAQ5ldR06wCpChQCjA==
@@ -2424,13 +2385,6 @@ async-function@^1.0.0:
resolved "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz"
integrity sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==
async-mutex@^0.3.0:
version "0.3.2"
resolved "https://registry.npmjs.org/async-mutex/-/async-mutex-0.3.2.tgz"
integrity sha512-HuTK7E7MT7jZEh1P9GtRW9+aTWiDWWi9InbZ5hjxrnRa39KS4BW04+xLBhYNS2aXhHUIKZSw3gj4Pn1pj+qGAA==
dependencies:
tslib "^2.3.1"
async@^3.2.4, async@^3.2.6:
version "3.2.6"
resolved "https://registry.npmjs.org/async/-/async-3.2.6.tgz"
@@ -2533,11 +2487,6 @@ bidi-js@^1.0.3:
dependencies:
require-from-string "^2.0.2"
big-integer@^1.6.48:
version "1.6.52"
resolved "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz"
integrity sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==
binary-extensions@^2.0.0:
version "2.3.0"
resolved "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz"
@@ -2701,13 +2650,6 @@ buffer@^6.0.3:
base64-js "^1.3.1"
ieee754 "^1.2.1"
bufferutil@^4.0.1, bufferutil@^4.0.3:
version "4.1.0"
resolved "https://registry.npmjs.org/bufferutil/-/bufferutil-4.1.0.tgz"
integrity sha512-ZMANVnAixE6AWWnPzlW2KpUrxhm9woycYvPOo67jWHyFowASTEd9s+QN1EIMsSDtwhIxN4sWE1jotpuDUIgyIw==
dependencies:
node-gyp-build "^4.3.0"
builtin-status-codes@^3.0.0:
version "3.0.0"
resolved "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz"
@@ -3132,14 +3074,6 @@ csstype@^3.2.2:
resolved "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz"
integrity sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==
d@1, d@^1.0.1, d@^1.0.2:
version "1.0.2"
resolved "https://registry.npmjs.org/d/-/d-1.0.2.tgz"
integrity sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==
dependencies:
es5-ext "^0.10.64"
type "^2.7.2"
data-uri-to-buffer@^6.0.2:
version "6.0.2"
resolved "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz"
@@ -3187,13 +3121,6 @@ debug@4, debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, d
dependencies:
ms "^2.1.3"
debug@^2.2.0:
version "2.6.9"
resolved "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz"
integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
dependencies:
ms "2.0.0"
debug@^3.2.7:
version "3.2.7"
resolved "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz"
@@ -3343,15 +3270,6 @@ dom-accessibility-api@^0.6.3:
resolved "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz"
integrity sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==
dom-serializer@^1.0.1:
version "1.4.1"
resolved "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz"
integrity sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==
dependencies:
domelementtype "^2.0.1"
domhandler "^4.2.0"
entities "^2.0.0"
dom-serializer@^2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz"
@@ -3366,18 +3284,11 @@ domain-browser@4.22.0:
resolved "https://registry.npmjs.org/domain-browser/-/domain-browser-4.22.0.tgz"
integrity sha512-IGBwjF7tNk3cwypFNH/7bfzBcgSCbaMOD3GsaY1AU/JRrnHnYgEM0+9kQt52iZxjNsjBtJYtao146V+f8jFZNw==
domelementtype@^2.0.1, domelementtype@^2.2.0, domelementtype@^2.3.0:
domelementtype@^2.3.0:
version "2.3.0"
resolved "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz"
integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==
domhandler@^4.0.0, domhandler@^4.2.0:
version "4.3.1"
resolved "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz"
integrity sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==
dependencies:
domelementtype "^2.2.0"
domhandler@^5.0.2, domhandler@^5.0.3:
version "5.0.3"
resolved "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz"
@@ -3385,15 +3296,6 @@ domhandler@^5.0.2, domhandler@^5.0.3:
dependencies:
domelementtype "^2.3.0"
domutils@^2.5.2:
version "2.8.0"
resolved "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz"
integrity sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==
dependencies:
dom-serializer "^1.0.1"
domelementtype "^2.2.0"
domhandler "^4.2.0"
domutils@^3.0.1, domutils@^3.2.2:
version "3.2.2"
resolved "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz"
@@ -3519,11 +3421,6 @@ engine.io-parser@~5.2.1:
resolved "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz"
integrity sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==
entities@^2.0.0:
version "2.2.0"
resolved "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz"
integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==
entities@^4.2.0:
version "4.5.0"
resolved "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz"
@@ -3676,33 +3573,6 @@ es-to-primitive@^1.3.0:
is-date-object "^1.0.5"
is-symbol "^1.0.4"
es5-ext@^0.10.35, es5-ext@^0.10.62, es5-ext@^0.10.63, es5-ext@^0.10.64, es5-ext@~0.10.14:
version "0.10.64"
resolved "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz"
integrity sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==
dependencies:
es6-iterator "^2.0.3"
es6-symbol "^3.1.3"
esniff "^2.0.1"
next-tick "^1.1.0"
es6-iterator@^2.0.3:
version "2.0.3"
resolved "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz"
integrity sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==
dependencies:
d "1"
es5-ext "^0.10.35"
es6-symbol "^3.1.1"
es6-symbol@^3.1.1, es6-symbol@^3.1.3:
version "3.1.4"
resolved "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.4.tgz"
integrity sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==
dependencies:
d "^1.0.2"
ext "^1.7.0"
esbuild@^0.27.0:
version "0.27.2"
resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz"
@@ -3932,16 +3802,6 @@ eslint@^9.39.2:
natural-compare "^1.4.0"
optionator "^0.9.3"
esniff@^2.0.1:
version "2.0.1"
resolved "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz"
integrity sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==
dependencies:
d "^1.0.1"
es5-ext "^0.10.62"
event-emitter "^0.3.5"
type "^2.7.2"
espree@^10.0.1, espree@^10.4.0:
version "10.4.0"
resolved "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz"
@@ -3997,14 +3857,6 @@ esutils@^2.0.2:
resolved "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz"
integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==
event-emitter@^0.3.5:
version "0.3.5"
resolved "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz"
integrity sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==
dependencies:
d "1"
es5-ext "~0.10.14"
event-target-shim@^5.0.0:
version "5.0.1"
resolved "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz"
@@ -4080,13 +3932,6 @@ expect@^30.2.0:
jest-mock "30.2.0"
jest-util "30.2.0"
ext@^1.7.0:
version "1.7.0"
resolved "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz"
integrity sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==
dependencies:
type "^2.7.2"
extend@^3.0.0:
version "3.0.2"
resolved "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz"
@@ -4435,7 +4280,7 @@ gopd@^1.0.1, gopd@^1.2.0:
resolved "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz"
integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==
graceful-fs@^4.1.11, graceful-fs@^4.2.0, graceful-fs@^4.2.11:
graceful-fs@^4.2.0, graceful-fs@^4.2.11:
version "4.2.11"
resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz"
integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==
@@ -4624,16 +4469,6 @@ htmlparser2@^10.1.0:
domutils "^3.2.2"
entities "^7.0.1"
htmlparser2@^6.1.0:
version "6.1.0"
resolved "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz"
integrity sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==
dependencies:
domelementtype "^2.0.1"
domhandler "^4.0.0"
domutils "^2.5.2"
entities "^2.0.0"
http-proxy-agent@^7.0.0, http-proxy-agent@^7.0.1, http-proxy-agent@^7.0.2:
version "7.0.2"
resolved "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz"
@@ -4699,7 +4534,7 @@ immediate@~3.0.5:
resolved "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz"
integrity sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==
immer@^11.0.0, immer@^11.1.3:
immer@^11.0.0:
version "11.1.3"
resolved "https://registry.npmjs.org/immer/-/immer-11.1.3.tgz"
integrity sha512-6jQTc5z0KJFtr1UgFpIL3N9XSC3saRaI9PwWtzM2pSqkNGtiNkYY2OSwkOGDK2XcTRcLb1pi/aNkKZz0nxVH4Q==
@@ -5021,11 +4856,6 @@ is-typed-array@^1.1.13, is-typed-array@^1.1.14, is-typed-array@^1.1.15, is-typed
dependencies:
which-typed-array "^1.1.16"
is-typedarray@^1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz"
integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==
is-unicode-supported@^0.1.0:
version "0.1.0"
resolved "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz"
@@ -5852,11 +5682,6 @@ miller-rabin@^4.0.0:
bn.js "^4.0.0"
brorand "^1.0.1"
mime@^3.0.0:
version "3.0.0"
resolved "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz"
integrity sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==
min-indent@^1.0.0:
version "1.0.1"
resolved "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz"
@@ -5944,11 +5769,6 @@ modern-tar@^0.7.2:
resolved "https://registry.npmjs.org/modern-tar/-/modern-tar-0.7.3.tgz"
integrity sha512-4W79zekKGyYU4JXVmB78DOscMFaJth2gGhgfTl2alWE4rNe3nf4N2pqenQ0rEtIewrnD79M687Ouba3YGTLOvg==
ms@2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz"
integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==
ms@^2.1.1, ms@^2.1.3:
version "2.1.3"
resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz"
@@ -6007,23 +5827,6 @@ netmask@^2.0.2:
resolved "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz"
integrity sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==
next-tick@^1.1.0:
version "1.1.0"
resolved "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz"
integrity sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==
node-gyp-build@^4.3.0:
version "4.8.4"
resolved "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz"
integrity sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==
node-localstorage@^2.2.1:
version "2.2.1"
resolved "https://registry.npmjs.org/node-localstorage/-/node-localstorage-2.2.1.tgz"
integrity sha512-vv8fJuOUCCvSPjDjBLlMqYMHob4aGjkmrkaE42/mZr0VT+ZAU10jRF8oTnX9+pgU9/vYJ8P7YT3Vd6ajkmzSCw==
dependencies:
write-file-atomic "^1.1.4"
node-releases@^2.0.27:
version "2.0.27"
resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz"
@@ -6277,11 +6080,6 @@ package-json-from-dist@^1.0.0:
resolved "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz"
integrity sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==
pako@^2.0.3:
version "2.1.0"
resolved "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz"
integrity sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==
pako@~1.0.2, pako@~1.0.5:
version "1.0.11"
resolved "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz"
@@ -6655,11 +6453,6 @@ punycode@^2.1.0, punycode@^2.3.1:
resolved "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz"
integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==
qrcode.react@^4.2.0:
version "4.2.0"
resolved "https://registry.npmjs.org/qrcode.react/-/qrcode.react-4.2.0.tgz"
integrity sha512-QpgqWi8rD9DsS9EP3z7BT+5lY5SFhsqGjpgW5DY/i3mK4M9DTBNz3ErMi8BWYEfI3L0d8GIbGmcdFAS1uIRGjA==
qs@^6.12.3:
version "6.14.1"
resolved "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz"
@@ -6847,11 +6640,6 @@ readdirp@~3.6.0:
dependencies:
picomatch "^2.2.1"
real-cancellable-promise@^1.1.1:
version "1.2.3"
resolved "https://registry.npmjs.org/real-cancellable-promise/-/real-cancellable-promise-1.2.3.tgz"
integrity sha512-hBI5Gy/55VEeeMtImMgEirD7eq5UmqJf1J8dFZtbJZA/3rB0pYFZ7PayMGueb6v4UtUtpKpP+05L0VwyE1hI9Q==
recursive-readdir@^2.2.3:
version "2.2.3"
resolved "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.3.tgz"
@@ -7280,11 +7068,6 @@ slash@^3.0.0:
resolved "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz"
integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==
slide@^1.1.5:
version "1.1.6"
resolved "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz"
integrity sha512-NwrtjCg+lZoqhFU8fOwl4ay2ei8PaqCBOUV3/ektPY9trO1yQ1oXEfmHAhKArUVUr/hOHvy5f6AdP17dCM0zMw==
smart-buffer@^4.2.0:
version "4.2.0"
resolved "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz"
@@ -7317,7 +7100,7 @@ socks-proxy-agent@^8.0.5:
debug "^4.3.4"
socks "^2.8.3"
socks@^2.6.2, socks@^2.8.3:
socks@^2.8.3:
version "2.8.7"
resolved "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz"
integrity sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==
@@ -7406,11 +7189,6 @@ stop-iteration-iterator@^1.1.0:
es-errors "^1.3.0"
internal-slot "^1.1.0"
store2@^2.13.0:
version "2.14.4"
resolved "https://registry.npmjs.org/store2/-/store2-2.14.4.tgz"
integrity sha512-srTItn1GOvyvOycgxjAnPA63FZNwy0PTyUBFMHRM+hVFltAeoh0LmNBz9SZqUS9mMqGk8rfyWyXn3GH5ReJ8Zw==
stream-browserify@^3.0.0:
version "3.0.0"
resolved "https://registry.npmjs.org/stream-browserify/-/stream-browserify-3.0.0.tgz"
@@ -7708,29 +7486,6 @@ tar-stream@^3.0.0, tar-stream@^3.1.5:
fast-fifo "^1.2.0"
streamx "^2.15.0"
telegram@^2.26.22:
version "2.26.22"
resolved "https://registry.npmjs.org/telegram/-/telegram-2.26.22.tgz"
integrity sha512-EIj7Yrjiu0Yosa3FZ/7EyPg9s6UiTi/zDQrFmR/2Mg7pIUU+XjAit1n1u9OU9h2oRnRM5M+67/fxzQluZpaJJg==
dependencies:
"@cryptography/aes" "^0.1.1"
async-mutex "^0.3.0"
big-integer "^1.6.48"
buffer "^6.0.3"
htmlparser2 "^6.1.0"
mime "^3.0.0"
node-localstorage "^2.2.1"
pako "^2.0.3"
path-browserify "^1.0.1"
real-cancellable-promise "^1.1.1"
socks "^2.6.2"
store2 "^2.13.0"
ts-custom-error "^3.2.0"
websocket "^1.0.34"
optionalDependencies:
bufferutil "^4.0.3"
utf-8-validate "^5.0.5"
text-decoder@^1.1.0:
version "1.2.3"
resolved "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz"
@@ -7854,11 +7609,6 @@ ts-api-utils@^2.4.0:
resolved "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz"
integrity sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==
ts-custom-error@^3.2.0:
version "3.3.1"
resolved "https://registry.npmjs.org/ts-custom-error/-/ts-custom-error-3.3.1.tgz"
integrity sha512-5OX1tzOjxWEgsr/YEUWSuPrQ00deKLh6D7OTWcvNHm12/7QPyRh8SYpyWvA4IZv8H/+GQWQEh/kwo95Q9OVW1A==
ts-interface-checker@^0.1.9:
version "0.1.13"
resolved "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz"
@@ -7874,7 +7624,7 @@ tsconfig-paths@^3.15.0:
minimist "^1.2.6"
strip-bom "^3.0.0"
tslib@^2.0.1, tslib@^2.1.0, tslib@^2.3.1:
tslib@^2.0.1, tslib@^2.1.0:
version "2.8.1"
resolved "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz"
integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==
@@ -7923,11 +7673,6 @@ type-fest@^5.2.0:
dependencies:
tagged-tag "^1.0.0"
type@^2.7.2:
version "2.7.3"
resolved "https://registry.npmjs.org/type/-/type-2.7.3.tgz"
integrity sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==
typed-array-buffer@^1.0.3:
version "1.0.3"
resolved "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz"
@@ -7973,13 +7718,6 @@ typed-array-length@^1.0.7:
possible-typed-array-names "^1.0.0"
reflect.getprototypeof "^1.0.6"
typedarray-to-buffer@^3.1.5:
version "3.1.5"
resolved "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz"
integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==
dependencies:
is-typedarray "^1.0.0"
typescript@~5.8.3:
version "5.8.3"
resolved "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz"
@@ -8114,13 +7852,6 @@ userhome@1.0.1:
resolved "https://registry.npmjs.org/userhome/-/userhome-1.0.1.tgz"
integrity sha512-5cnLm4gseXjAclKowC4IjByaGsjtAoV6PrOQOljplNB54ReUYJP8HdAFq2muHinSDAh09PPX/uXDPfdxRHvuSA==
utf-8-validate@^5.0.2, utf-8-validate@^5.0.5:
version "5.0.10"
resolved "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz"
integrity sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==
dependencies:
node-gyp-build "^4.3.0"
util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1:
version "1.0.2"
resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz"
@@ -8290,18 +8021,6 @@ webidl-conversions@^8.0.1:
resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz"
integrity sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==
websocket@^1.0.34:
version "1.0.35"
resolved "https://registry.npmjs.org/websocket/-/websocket-1.0.35.tgz"
integrity sha512-/REy6amwPZl44DDzvRCkaI1q1bIiQB0mEFQLUrhz3z2EK91cp3n72rAjUlrTP0zV22HJIUOVHQGPxhFRjxjt+Q==
dependencies:
bufferutil "^4.0.1"
debug "^2.2.0"
es5-ext "^0.10.63"
typedarray-to-buffer "^3.1.5"
utf-8-validate "^5.0.2"
yaeti "^0.0.6"
whatwg-encoding@^3.1.1:
version "3.1.1"
resolved "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz"
@@ -8454,15 +8173,6 @@ wrappy@1:
resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz"
integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==
write-file-atomic@^1.1.4:
version "1.3.4"
resolved "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-1.3.4.tgz"
integrity sha512-SdrHoC/yVBPpV0Xq/mUZQIpW2sWXAShb/V4pomcJXh92RuaO+f3UTWItiR3Px+pLnV2PvC2/bfn5cwr5X6Vfxw==
dependencies:
graceful-fs "^4.1.11"
imurmurhash "^0.1.4"
slide "^1.1.5"
ws@^8.8.0:
version "8.19.0"
resolved "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz"
@@ -8503,11 +8213,6 @@ y18n@^5.0.5:
resolved "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz"
integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==
yaeti@^0.0.6:
version "0.0.6"
resolved "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz"
integrity sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug==
yallist@^3.0.2:
version "3.1.1"
resolved "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz"
@@ -8606,11 +8311,6 @@ zip-stream@^6.0.1:
resolved "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz"
integrity sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==
zustand@^5.0.10:
version "5.0.10"
resolved "https://registry.npmjs.org/zustand/-/zustand-5.0.10.tgz"
integrity sha512-U1AiltS1O9hSy3rul+Ub82ut2fqIAefiSuwECWt6jlMVUGejvf+5omLcRBSzqbRagSM3hQZbtzdeRc6QVScXTg==
zwitch@^2.0.0:
version "2.0.4"
resolved "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz"